Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
58b8325
feat(devices): add remote platform erase (RPE) feature
nmgaston Apr 21, 2026
3c50c8b
fix: fix build and test issues
nmgaston May 7, 2026
50993b6
feat: remote platform erase improvements
nmgaston May 7, 2026
f872ff3
fix: fix renaming issue for rpe
nmgaston May 14, 2026
b9f92cd
test: skip RPE tests if ran in cloud mode
nmgaston May 15, 2026
3e9c0ef
Merge remote-tracking branch 'origin/main' into remotePlatformErase
nmgaston May 28, 2026
712fec4
chore(devices): cleanup after rebase
nmgaston May 28, 2026
80c4d77
fix: revert back to head changes
nmgaston May 29, 2026
e82a9a2
fix: send payload from server for error messages
nmgaston Jun 2, 2026
3b98195
feat: add success messages for os clear success and CSME clear success
nmgaston Jun 2, 2026
b333f9c
Merge branch 'main' into remotePlatformErase
nmgaston Jun 10, 2026
39886c9
feat: add pyrite disk encryption for SSD erase
nmgaston Jun 10, 2026
c280bef
Merge branch 'remotePlatformErase' of https://github.com/device-manag…
nmgaston Jun 10, 2026
a41e64e
feat: add user consent code for RPE
nmgaston Jun 12, 2026
81ca705
Merge branch 'main' into remotePlatformErase
nmgaston Jun 15, 2026
ccf6bcc
fix: language translation missed
nmgaston Jun 15, 2026
b440de8
Merge branch 'remotePlatformErase' of https://github.com/device-manag…
nmgaston Jun 15, 2026
f5d1840
Revert "feat: add user consent code for RPE"
nmgaston Jun 18, 2026
9e7c36a
revert: revert user consent and blocking rpe changes for cloud mode
nmgaston Jun 18, 2026
7cb4ebb
Merge branch 'main' into remotePlatformErase
nmgaston Jun 24, 2026
2390145
fix: build after merge
nmgaston Jun 24, 2026
7210e13
chore(docker): update Dockerfile
nmgaston Jun 26, 2026
b65818b
revert(docker): revert Dockerfile update
nmgaston Jun 26, 2026
9ba37cd
feat: add user consent mode for CCM mode and when ACM UserConsent mod…
nmgaston Jun 26, 2026
54c1fc4
fix: issue with keeping RPE state between tabs in cloud
nmgaston Jun 30, 2026
0f5100d
Merge branch 'main' into remotePlatformErase
nmgaston Jun 30, 2026
67d7624
style: UI changes from review
nmgaston Jul 1, 2026
9b6efa3
style: UI changes from review
nmgaston Jul 1, 2026
e51bdf4
Merge branch 'main' into remotePlatformErase
nmgaston Jul 10, 2026
f1e2174
feat: add 32 byte limit for Pyrite drive when wiping SSD that is encr…
nmgaston Jul 14, 2026
fac4bbb
chore: rebase
nmgaston Jul 14, 2026
2c0484a
chore: fix lint issue
nmgaston Jul 15, 2026
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
67 changes: 67 additions & 0 deletions cypress/e2e/fixtures/api/eventlog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,73 @@ const eventLogs = {
IDER: false
}
}
},
remotePlatformErase: {
notSupported: {
response: {
userConsent: 'none',
optInState: 0,
redirection: true,
KVM: true,
SOL: true,
IDER: false,
kvmAvailable: false,
ocr: false,
httpsBootSupported: false,
winREBootSupported: false,
localPBABootSupported: false,
rpe: false,
rpeSupported: false,
pbaBootFilesPath: [],
winREBootFilesPath: { instanceID: '', biosBootString: '', bootString: '' }
}
},
supportedDisabled: {
response: {
userConsent: 'none',
optInState: 0,
redirection: true,
KVM: true,
SOL: true,
IDER: false,
kvmAvailable: false,
ocr: false,
httpsBootSupported: false,
winREBootSupported: false,
localPBABootSupported: false,
rpe: false,
rpeSupported: true,
pbaBootFilesPath: [],
winREBootFilesPath: { instanceID: '', biosBootString: '', bootString: '' }
}
},
supportedEnabled: {
response: {
userConsent: 'none',
optInState: 0,
redirection: true,
KVM: true,
SOL: true,
IDER: false,
kvmAvailable: false,
ocr: false,
httpsBootSupported: false,
winREBootSupported: false,
localPBABootSupported: false,
rpe: true,
rpeSupported: true,
pbaBootFilesPath: [],
winREBootFilesPath: { instanceID: '', biosBootString: '', bootString: '' }
}
},
capabilities: {
response: {
secureEraseAllSSDs: true,
tpmClear: false,
restoreBIOSToEOM: true,
unconfigureCSME: false
}
}
}
}

Expand Down
258 changes: 258 additions & 0 deletions cypress/e2e/integration/device/remote-platform-erase.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
/*********************************************************************
* Copyright (c) Intel Corporation 2022
* SPDX-License-Identifier: Apache-2.0
**********************************************************************/

import { httpCodes } from '../../fixtures/api/httpCodes'
import { eventLogs } from '../../fixtures/api/eventlog'

const describeWhenNotCloud = Cypress.env('CLOUD') ? describe.skip : describe

const navigateToRemotePlatformErase = (
featuresBody: object = eventLogs.remotePlatformErase.supportedDisabled.response
): void => {
cy.myIntercept('GET', /.*amt\/features.*/, {
statusCode: httpCodes.SUCCESS,
body: featuresBody
}).as('get-features')

cy.myIntercept('GET', 'devices?$top=25&$skip=0&$count=true', {
statusCode: httpCodes.SUCCESS,
body: eventLogs.devices.success.response
}).as('get-devices')

cy.myIntercept('GET', /devices\/.*$/, {
statusCode: httpCodes.SUCCESS,
body: eventLogs.devices.success.response
}).as('get-device-by-id')

cy.myIntercept('GET', /devices\/tags/, {
statusCode: httpCodes.SUCCESS,
body: []
}).as('get-tags')

cy.myIntercept('GET', /.*power.*/, {
statusCode: httpCodes.SUCCESS,
body: { powerstate: 2 }
}).as('get-powerstate')

cy.myIntercept('GET', /.*general.*/, {
statusCode: httpCodes.SUCCESS,
body: eventLogs.general.success.response
}).as('get-general')

cy.myIntercept('GET', /.*version.*/, {
statusCode: httpCodes.SUCCESS,
body: eventLogs.version.success.response
}).as('get-version')

cy.myIntercept('GET', /.*amt\/boot\/remoteErase.*/, {
statusCode: httpCodes.SUCCESS,
body: eventLogs.remotePlatformErase.capabilities.response
}).as('get-capabilities')

cy.myIntercept('GET', /.*alarmOccurrences.*/, {
statusCode: httpCodes.SUCCESS,
body: eventLogs.alarmOccurrences.success.response
}).as('get-alarms')

cy.goToPage('Devices')
cy.wait('@get-devices').its('response.statusCode').should('eq', 200)

cy.get('mat-row').first().click()
cy.wait('@get-device-by-id').its('response.statusCode').should('eq', 200)

cy.get('.mat-mdc-list-item-title').contains('Remote Platform Erase').click()
cy.wait('@get-capabilities')
// Wait for Angular to finish loading (isLoading signal → mat-progress-bar disappears)
cy.get('mat-progress-bar').should('not.exist')
}

describeWhenNotCloud('Remote Platform Erase', () => {
beforeEach(() => {
cy.setup()
})

it('should show "not supported" message when remoteEraseSupported is false', () => {
navigateToRemotePlatformErase(eventLogs.remotePlatformErase.notSupported.response)
cy.wait('@get-features')

cy.get('[data-cy="remoteEraseCheckbox"]').should('not.exist')
cy.get('mat-icon').contains('info').should('exist')
})

it('should show checkbox when supported but not enabled', () => {
navigateToRemotePlatformErase(eventLogs.remotePlatformErase.supportedDisabled.response)
cy.wait('@get-features')

cy.get('[data-cy="remoteEraseCheckbox"]').should('exist')
cy.get('[data-cy="remoteEraseCheckbox"] button[role="switch"]').should('have.attr', 'aria-checked', 'false')
cy.get('[data-cy="initiateEraseButton"]').should('not.exist')
})

it('should show checkbox checked when rpe is true in API response', () => {
navigateToRemotePlatformErase(eventLogs.remotePlatformErase.supportedEnabled.response)
cy.wait('@get-features')

cy.get('[data-cy="remoteEraseCheckbox"] button[role="switch"]').should('have.attr', 'aria-checked', 'true')
cy.get('[data-cy="initiateEraseButton"]').should('be.visible')
})

it('should enable the feature and show initiate erase button after toggling on', () => {
cy.myIntercept('POST', /.*amt\/features.*/, {
statusCode: httpCodes.SUCCESS,
body: eventLogs.remotePlatformErase.supportedEnabled.response
}).as('post-features')

navigateToRemotePlatformErase()
cy.wait('@get-features')

cy.get('[data-cy="remoteEraseCheckbox"] button[role="switch"]')
.should('not.have.attr', 'aria-disabled', 'true')
.click()
cy.wait('@post-features').its('request.body.rpe').should('eq', true)

cy.get('[data-cy="initiateEraseButton"]').should('be.visible')
})

it('should disable the feature after toggling off', () => {
// Register disable handler first (LIFO: last registered = first matched)
// so the second POST (disable click) hits the disable response
cy.intercept(
{ method: 'POST', url: /.*amt\/features.*/, times: 1 },
{
statusCode: httpCodes.SUCCESS,
body: eventLogs.remotePlatformErase.supportedDisabled.response
}
).as('post-features-disable')

// Register enable handler second so it matches the first POST (enable click)
cy.intercept(
{ method: 'POST', url: /.*amt\/features.*/, times: 1 },
{
statusCode: httpCodes.SUCCESS,
body: eventLogs.remotePlatformErase.supportedEnabled.response
}
).as('post-features-enable')

navigateToRemotePlatformErase()
cy.wait('@get-features')

// Enable first
cy.get('[data-cy="remoteEraseCheckbox"] button[role="switch"]')
.should('not.have.attr', 'aria-disabled', 'true')
.click()
cy.wait('@post-features-enable').its('request.body.rpe').should('eq', true)

// Then disable
cy.get('[data-cy="remoteEraseCheckbox"] button[role="switch"]')
.should('not.have.attr', 'aria-disabled', 'true')
.click()
cy.wait('@post-features-disable').its('request.body.rpe').should('eq', false)

cy.get('[data-cy="initiateEraseButton"]').should('not.exist')
})

it('should call the remoteErase API when erase is confirmed in the dialog', () => {
cy.myIntercept('POST', /.*amt\/features.*/, {
statusCode: httpCodes.SUCCESS,
body: eventLogs.remotePlatformErase.supportedEnabled.response
}).as('post-features')

cy.myIntercept('POST', /.*amt\/boot\/remoteErase.*/, {
statusCode: httpCodes.SUCCESS,
body: {}
}).as('post-erase')

navigateToRemotePlatformErase()
cy.wait('@get-features')

// Enable the feature first
cy.get('[data-cy="remoteEraseCheckbox"] button[role="switch"]')
.should('not.have.attr', 'aria-disabled', 'true')
.click()
cy.wait('@post-features')

cy.get('[data-cy="eraseCapCheckbox"]').first().find('input[type="checkbox"]').check({ force: true })
cy.get('[data-cy="initiateEraseButton"]').click()

cy.get('mat-dialog-container').should('be.visible')
cy.get('mat-dialog-container').contains('button', 'Yes').click()

cy.wait('@post-erase').its('response.statusCode').should('eq', 200)
})

it('should not call the remoteErase API when erase is cancelled', () => {
cy.myIntercept('POST', /.*amt\/features.*/, {
statusCode: httpCodes.SUCCESS,
body: eventLogs.remotePlatformErase.supportedEnabled.response
}).as('post-features')

cy.myIntercept('POST', /.*amt\/boot\/remoteErase.*/, {
statusCode: httpCodes.SUCCESS,
body: {}
}).as('post-erase')

navigateToRemotePlatformErase()
cy.wait('@get-features')

// Enable the feature first
cy.get('[data-cy="remoteEraseCheckbox"] button[role="switch"]')
.should('not.have.attr', 'aria-disabled', 'true')
.click()
cy.wait('@post-features')

cy.get('[data-cy="eraseCapCheckbox"]').first().find('input[type="checkbox"]').check({ force: true })
cy.get('[data-cy="initiateEraseButton"]').click()

cy.get('mat-dialog-container').should('be.visible')
cy.get('mat-dialog-container').contains('button', 'No').click()

cy.get('@post-erase.all').should('have.length', 0)
})

it('should show error snackbar when toggling feature fails', () => {
cy.myIntercept('POST', /.*amt\/features.*/, {
statusCode: httpCodes.INTERNAL_SERVER_ERROR,
body: {}
}).as('post-features-error')

navigateToRemotePlatformErase()
cy.wait('@get-features')

cy.get('[data-cy="remoteEraseCheckbox"] button[role="switch"]')
.should('not.have.attr', 'aria-disabled', 'true')
.click()
cy.wait('@post-features-error')

cy.get('mat-snack-bar-container').should('be.visible')
})

it('should show error snackbar when erase API fails', () => {
cy.myIntercept('POST', /.*amt\/features.*/, {
statusCode: httpCodes.SUCCESS,
body: eventLogs.remotePlatformErase.supportedEnabled.response
}).as('post-features')

cy.myIntercept('POST', /.*amt\/boot\/remoteErase.*/, {
statusCode: httpCodes.INTERNAL_SERVER_ERROR,
body: {}
}).as('post-erase-error')

navigateToRemotePlatformErase()
cy.wait('@get-features')

// Enable the feature first
cy.get('[data-cy="remoteEraseCheckbox"] button[role="switch"]')
.should('not.have.attr', 'aria-disabled', 'true')
.click()
cy.wait('@post-features')

cy.get('[data-cy="eraseCapCheckbox"]').first().find('input[type="checkbox"]').check({ force: true })
cy.get('[data-cy="initiateEraseButton"]').click()
cy.get('mat-dialog-container').contains('button', 'Yes').click()

cy.wait('@post-erase-error')
cy.get('mat-snack-bar-container').should('be.visible')
})
})
3 changes: 3 additions & 0 deletions src/app/devices/device-detail/device-detail.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ <h3 matListItemTitle>{{ item.name | translate }}</h3>
@case ('tls') {
<app-tls [deviceId]="deviceId"></app-tls>
}
@case ('remote-platform-erase') {
<app-remote-platform-erase [deviceId]="deviceId"></app-remote-platform-erase>
}
}
</mat-sidenav-content>
</mat-sidenav-container>
8 changes: 8 additions & 0 deletions src/app/devices/device-detail/device-detail.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { GeneralComponent } from '../general/general.component'
import { NetworkSettingsComponent } from '../network-settings/network-settings.component'
import { environment } from '../../../environments/environment'
import { TLSComponent } from '../tls/tls.component'
import { RemotePlatformEraseComponent } from '../remote-platform-erase/remote-platform-erase.component'
import { TranslatePipe } from '@ngx-translate/core'

@Component({
Expand Down Expand Up @@ -58,6 +59,7 @@ import { TranslatePipe } from '@ngx-translate/core'
RouterLinkActive,
NetworkSettingsComponent,
TLSComponent,
RemotePlatformEraseComponent,
TranslatePipe
]
})
Expand Down Expand Up @@ -117,6 +119,12 @@ export class DeviceDetailComponent implements OnInit, OnDestroy {
description: 'deviceDetail.certificatesDescription.value',
component: 'certificates',
icon: 'verified'
},
{
name: 'deviceDetail.remotePlatformErase.value',
description: 'deviceDetail.remotePlatformEraseDescription.value',
component: 'remote-platform-erase',
icon: 'computer_cancel'
}
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ describe('DeviceToolbarComponent', () => {
kvmAvailable: true,
winREBootSupported: true,
localPBABootSupported: true,
remoteErase: true,
rpe: true,
rpeSupported: true,
pbaBootFilesPath: [],
winREBootFilesPath: {
instanceID: '',
Expand Down
Loading
Loading