@@ -7,7 +7,7 @@ import { Component, OnInit, inject, signal, input, DestroyRef } from '@angular/c
77import { catchError , finalize , switchMap } from 'rxjs/operators'
88import { MatSnackBar } from '@angular/material/snack-bar'
99import { Router } from '@angular/router'
10- import { Observable , of , forkJoin , EMPTY } from 'rxjs'
10+ import { Observable , of , forkJoin , EMPTY , timer } from 'rxjs'
1111import { DevicesService } from '../devices.service'
1212import SnackbarDefaults from '../../shared/config/snackBarDefault'
1313import { AMTFeaturesResponse , BootDetails , Device , UserConsentResponse } from '../../../models/models'
@@ -217,6 +217,22 @@ export class DeviceToolbarComponent implements OnInit {
217217 this . powerOptions . set ( options )
218218 }
219219
220+ private setOptimisticPowerState ( action : number ) : void {
221+ // Immediately reflect the expected outcome so the UI doesn't show the
222+ // old state while waiting for the device to transition.
223+ switch ( action ) {
224+ case 2 : // power on
225+ this . powerState . set ( 'deviceToolbar.power.on.value' )
226+ break
227+ case 8 : // power off
228+ case 12 : // soft off
229+ this . powerState . set ( 'deviceToolbar.power.off.value' )
230+ break
231+ default : // reset, cycle, etc. — state is transitioning
232+ this . powerState . set ( 'Unknown' )
233+ }
234+ }
235+
220236 private setPowerStateLabel ( powerstate : number ) : void {
221237 switch ( powerstate ) {
222238 case 2 :
@@ -233,39 +249,41 @@ export class DeviceToolbarComponent implements OnInit {
233249
234250 private loadPowerState ( ) : void {
235251 // Use cached to dedupe simultaneous requests (KVM deep-link + toolbar both loading).
236- this . isLoading ( ) . set ( true )
237- this . devicesService
238- . getPowerStateCached ( this . deviceId ( ) )
239- . pipe (
240- catchError ( ( ) => {
241- // Network/backend failure; silently skip update and let UI show previous state.
242- return EMPTY
243- } ) ,
244- finalize ( ( ) => {
245- this . isLoading ( ) . set ( false )
246- } ) ,
247- takeUntilDestroyed ( this . destroyRef )
248- )
249- . subscribe ( ( powerState ) => {
250- this . setPowerStateLabel ( powerState . powerstate )
251- } )
252+ this . fetchPowerState ( this . devicesService . getPowerStateCached ( this . deviceId ( ) ) )
252253 }
253254
254255 refreshPowerState ( ) : void {
255- this . isLoading ( ) . set ( true )
256- // Bypass the cache so manual refresh always fetches the latest state from the
257- // device rather than returning a previously cached value.
258- this . devicesService
259- . getPowerState ( this . deviceId ( ) )
256+ // Bypass the cache so manual refresh always fetches the latest state from the device.
257+ this . fetchPowerState ( this . devicesService . getPowerState ( this . deviceId ( ) ) , false , ( ) => {
258+ const msg : string = this . translate . instant ( 'kvm.errorRetrievePower.value' )
259+ this . snackBar . open ( msg , undefined , SnackbarDefaults . defaultError )
260+ } )
261+ }
262+
263+ private silentRefreshPowerState ( ) : void {
264+ // Refresh without showing the loading spinner — used after a power action
265+ // where the optimistic state is already shown to the user.
266+ this . fetchPowerState ( this . devicesService . getPowerState ( this . deviceId ( ) ) , true )
267+ }
268+
269+ private fetchPowerState ( source : Observable < any > , silent = false , onError ?: ( ) => void ) : void {
270+ if ( ! silent ) {
271+ this . isLoading ( ) . set ( true )
272+ }
273+
274+ source
260275 . pipe (
261276 catchError ( ( err ) => {
262- console . error ( err )
263- const msg : string = this . translate . instant ( 'kvm.errorRetrievePower.value' )
264- this . snackBar . open ( msg , undefined , SnackbarDefaults . defaultError )
277+ if ( onError ) {
278+ console . error ( err )
279+ onError ( )
280+ }
265281 return EMPTY
266282 } ) ,
267283 finalize ( ( ) => {
268- this . isLoading ( ) . set ( false )
284+ if ( ! silent ) {
285+ this . isLoading ( ) . set ( false )
286+ }
269287 } ) ,
270288 takeUntilDestroyed ( this . destroyRef )
271289 )
@@ -475,6 +493,14 @@ export class DeviceToolbarComponent implements OnInit {
475493 const msg : string = this . translate . instant ( 'devices.powerActionSent.value' )
476494 console . log ( 'Power action sent successfully:' , data )
477495 this . snackBar . open ( msg , undefined , SnackbarDefaults . defaultSuccess )
496+ // Set expected state immediately for instant feedback.
497+ this . setOptimisticPowerState ( action )
498+ // Then silently confirm actual state after device transitions — no loading spinner.
499+ timer ( 5000 )
500+ . pipe ( takeUntilDestroyed ( this . destroyRef ) )
501+ . subscribe ( ( ) => {
502+ this . silentRefreshPowerState ( )
503+ } )
478504 } else {
479505 console . log ( 'Power action failed:' , data )
480506 const msg : string = this . translate . instant ( 'devices.failPowerAction.value' )
0 commit comments