1- import { Component , ElementRef , OnInit , ViewChild } from '@angular/core' ;
1+ import { Component , ElementRef , OnDestroy , OnInit , ViewChild } from '@angular/core' ;
22import { HttpService , I18nService , SettingService } from '@app/services' ;
33import { ActivatedRoute } from '@angular/router' ;
44import { Asset , Session , Ticket , User } from '@app/model' ;
55import { NzNotificationService } from 'ng-zorro-antd/notification' ;
66import { getWaterMarkContent } from '@app/utils/common' ;
77import { joinEndpointUrl } from '@app/utils/path' ;
8+ import { combineLatest , firstValueFrom , from , Subject , TimeoutError } from 'rxjs' ;
9+ import { takeUntil , timeout } from 'rxjs/operators' ;
810
911@Component ( {
1012 standalone : false ,
1113 selector : 'pages-monitor' ,
1214 templateUrl : 'monitor.component.html' ,
1315 styleUrls : [ 'monitor.component.scss' ]
1416} )
15- export class PagesMonitorComponent implements OnInit {
16- @ViewChild ( 'contentWindow' , { static : false } ) windowRef : ElementRef ;
17+ export class PagesMonitorComponent implements OnInit , OnDestroy {
18+ @ViewChild ( 'contentWindow' , { static : true } ) windowRef : ElementRef ;
1719 iframeURL : string ;
1820 sessionDetail : Session = null ;
1921 sessionID : string ;
@@ -22,76 +24,158 @@ export class PagesMonitorComponent implements OnInit {
2224 ticketDetail : Ticket ;
2325 supportedLock : boolean = false ;
2426 user : User ;
27+ loading = false ;
28+ errorMessage = '' ;
29+ loginRequired = false ;
30+ loadingStage = '' ;
31+ private readonly destroy$ = new Subject < void > ( ) ;
32+ private readonly loadCancelled$ = new Subject < void > ( ) ;
33+ private requestController : AbortController ;
34+ private loadSequence = 0 ;
35+ private readonly requestTimeout = 15000 ;
2536
2637 constructor ( private _settingSvc : SettingService ,
2738 private _http : HttpService ,
2839 private _route : ActivatedRoute ,
2940 private _toastr : NzNotificationService ,
30- private _i18n : I18nService ) {
31- this . getCurrentUser ( ) ;
41+ private _i18n : I18nService ) { }
42+
43+ ngOnInit ( ) {
44+ combineLatest ( [ this . _route . params , this . _route . queryParams ] )
45+ . pipe ( takeUntil ( this . destroy$ ) )
46+ . subscribe ( ( [ params , queryParams ] ) => {
47+ this . sessionID = params [ 'sid' ] ;
48+ this . ticketID = queryParams [ 'ticket_id' ] ;
49+ this . loadMonitor ( ) ;
50+ } ) ;
3251 }
3352
34- getCurrentUser ( ) {
35- this . _http . getUserProfile ( ) . then ( user => {
36- this . user = user ;
37- } ) ;
53+ ngOnDestroy ( ) {
54+ this . loadSequence ++ ;
55+ this . requestController ?. abort ( ) ;
56+ this . loadCancelled$ . next ( ) ;
57+ this . loadCancelled$ . complete ( ) ;
58+ this . destroy$ . next ( ) ;
59+ this . destroy$ . complete ( ) ;
3860 }
3961
40- async ngOnInit ( ) {
41- this . _route . params . subscribe ( params => {
42- this . sessionID = params [ 'sid' ] ;
43- this . generateMonitorURL ( ) . then ( async ( ) => {
44- const sessionObj = this . sessionDetail ;
45- let asset : any = null ;
46- try {
47- asset = await this . _http . getAssetDetail ( sessionObj . asset_id ) . toPromise ( ) ;
48- } catch ( error ) {
49- asset = new Asset ( ) ;
50- }
51- let sessionUser : User = null ;
52- try {
53- sessionUser = await this . _http . getUserDetail ( sessionObj . user_id ) ;
54- } catch ( error ) {
55- sessionUser = new User ( ) ;
62+ get loginUrl ( ) : string {
63+ return this . _http . getLoginUrl ( ) ;
64+ }
65+
66+ private waitFor < T > ( request : Promise < T > , milliseconds = this . requestTimeout ) : Promise < T > {
67+ return firstValueFrom ( from ( request ) . pipe ( timeout ( milliseconds ) , takeUntil ( this . loadCancelled$ ) ) ) ;
68+ }
69+
70+ private async optionalWatermarkDetail < T > ( request : Promise < T > , fallback : T ) : Promise < T > {
71+ try {
72+ return await this . waitFor ( request , 5000 ) || fallback ;
73+ } catch ( error ) {
74+ if ( error . status === 401 ) {
75+ throw error ;
76+ }
77+ // Keep the existing fallback for unavailable asset/session-user metadata.
78+ return fallback ;
79+ }
80+ }
81+
82+ async loadMonitor ( ) {
83+ const sequence = ++ this . loadSequence ;
84+ this . requestController ?. abort ( ) ;
85+ this . loadCancelled$ . next ( ) ;
86+ const controller = new AbortController ( ) ;
87+ this . requestController = controller ;
88+ const control = { signal : controller . signal , timeout : this . requestTimeout } ;
89+ this . loading = true ;
90+ this . errorMessage = '' ;
91+ this . loginRequired = false ;
92+ this . iframeURL = null ;
93+ this . sessionDetail = null ;
94+ this . ticketDetail = null ;
95+ this . supportedLock = false ;
96+ this . loadingStage = 'Session details' ;
97+ try {
98+ if ( ! this . sessionID ) {
99+ throw new Error ( 'Invalid session ID' ) ;
100+ }
101+ const session = await this . waitFor ( this . _http . getSessionDetail ( this . sessionID , control ) ) ;
102+ if ( sequence !== this . loadSequence ) {
103+ return ;
104+ }
105+ if ( ! session ?. terminal ?. type || ! session . type ?. value ) {
106+ throw new Error ( 'Invalid session response' ) ;
107+ }
108+ this . sessionDetail = session ;
109+ this . loadingStage = 'Monitor initialization' ;
110+ const protocol = window . location . protocol . replace ( ':' , '' ) ;
111+ const data = { assetId : '' , sessionId : this . sessionID , token : '' } ;
112+ const [ user , endpoint , , ticket ] = await this . waitFor ( Promise . all ( [
113+ this . _http . getUserProfile ( control ) ,
114+ this . _http . getSmartEndpoint ( data , protocol , control ) ,
115+ this . _settingSvc . init ( control ) ,
116+ this . ticketID ? this . _http . getTicketDetail ( this . ticketID , control ) : Promise . resolve ( null )
117+ ] ) ) ;
118+ if ( sequence !== this . loadSequence ) {
119+ return ;
120+ }
121+ if ( ! user ?. username || ! endpoint ) {
122+ throw new Error ( 'Invalid monitor response' ) ;
123+ }
124+ const iframeURL = this . generateMonitorURL ( endpoint . getUrl ( ) , session . terminal . type ) ;
125+ this . user = user ;
126+ this . ticketDetail = ticket ;
127+ if ( this . _settingSvc . globalSetting . SECURITY_WATERMARK_ENABLED ) {
128+ this . loadingStage = 'Watermark initialization' ;
129+ const detailControl = { ...control , timeout : 5000 } ;
130+ const [ asset , sessionUser ] = await Promise . all ( [
131+ this . optionalWatermarkDetail ( this . _http . getAssetDetail ( session . asset_id , detailControl ) . toPromise ( ) , new Asset ( ) ) ,
132+ this . optionalWatermarkDetail ( this . _http . getUserDetail ( session . user_id , detailControl ) , new User ( ) )
133+ ] ) ;
134+ if ( sequence !== this . loadSequence ) {
135+ return ;
56136 }
57- const auditorUser = `${ this . _i18n . instant ( 'Viewer' ) } : ${ this . user . name } (${ this . user . username } )` ;
137+ const auditorUser = `${ this . _i18n . instant ( 'Viewer' ) } : ${ user . name } (${ user . username } )` ;
58138 const sessionContent = getWaterMarkContent ( sessionUser , asset , this . _settingSvc ) ;
59- const content = `${ auditorUser } \n${ sessionContent } ` ;
60- this . _settingSvc . createWaterMarkIfNeed (
61- this . windowRef . nativeElement , content ) ;
62- } ) ;
63- } ) ;
64- this . _route . queryParams . subscribe ( params => {
65- this . ticketID = params [ 'ticket_id' ] ;
66- if ( this . ticketID ) {
67- this . _http . getTicketDetail ( this . ticketID ) . then ( ( res ) => {
68- this . ticketDetail = res ;
69- } ) ;
139+ await this . waitFor ( this . _settingSvc . createWaterMarkIfNeed (
140+ this . windowRef . nativeElement , `${ auditorUser } \n${ sessionContent } ` , control ) ) ;
141+ if ( sequence !== this . loadSequence ) {
142+ return ;
143+ }
144+ }
145+ this . isPaused = session . is_locked ;
146+ this . supportedLock = [ 'koko' , 'lion' , 'chen' ] . includes ( session . terminal . type ) && session . type . value === 'normal' ;
147+ this . iframeURL = iframeURL ;
148+ } catch ( error ) {
149+ if ( sequence !== this . loadSequence ) {
150+ return ;
70151 }
71- } ) ;
152+ this . loginRequired = error . status === 401 ;
153+ const messages = {
154+ 0 : 'Unable to reach the server. Check your network and retry.' ,
155+ 401 : 'Your login has expired or you are not signed in.' ,
156+ 403 : 'You do not have permission to access the requested resource.' ,
157+ 404 : 'The requested resource does not exist or is no longer available.'
158+ } ;
159+ this . errorMessage = error instanceof TimeoutError ? 'The request timed out. Please retry.' :
160+ messages [ error . status ] || 'Initialization failed. Please retry or contact the administrator.' ;
161+ console . warn ( 'Monitor initialization failed' , { stage : this . loadingStage , status : error . status } ) ;
162+ } finally {
163+ // Cancel every outstanding request in this attempt, including siblings of a failed request.
164+ controller . abort ( ) ;
165+ if ( sequence === this . loadSequence ) {
166+ this . loading = false ;
167+ }
168+ }
72169 }
73170
74- async generateMonitorURL ( ) {
75- this . sessionDetail = await this . _http . getSessionDetail ( this . sessionID ) ;
76- const supportedType = [ 'koko' , 'lion' , 'chen' ] ;
77- const isSupportComponent = supportedType . includes ( this . sessionDetail . terminal . type ) ;
78- const isNormalSession = this . sessionDetail . type . value === 'normal' ;
79- this . supportedLock = isSupportComponent && isNormalSession ;
80- this . isPaused = this . sessionDetail . is_locked ;
81- const protocol = window . location . protocol . replace ( ':' , '' ) ;
82- const data = { 'assetId' : '' , 'appId' : '' , 'sessionId' : this . sessionID , 'token' : '' } ;
83- const smartEndpoint = await this . _http . getSmartEndpoint ( data , protocol ) ;
84- const baseUrl = smartEndpoint . getUrl ( ) ;
85- const terminal_type = this . sessionDetail . terminal . type ;
86- switch ( terminal_type ) {
171+ private generateMonitorURL ( baseUrl : string , terminalType : string ) : string {
172+ switch ( terminalType ) {
87173 case 'razor' :
88- this . iframeURL = joinEndpointUrl ( baseUrl , `/razor/monitor/${ this . sessionID } /` ) ;
89- break ;
174+ return joinEndpointUrl ( baseUrl , `/razor/monitor/${ this . sessionID } /` ) ;
90175 case 'lion' :
91- this . iframeURL = joinEndpointUrl ( baseUrl , `/lion/monitor/?session=${ this . sessionID } ` ) ;
92- break ;
176+ return joinEndpointUrl ( baseUrl , `/lion/monitor/?session=${ this . sessionID } ` ) ;
93177 default :
94- this . iframeURL = joinEndpointUrl ( baseUrl , `/koko/monitor/${ this . sessionID } /` ) ;
178+ return joinEndpointUrl ( baseUrl , `/koko/monitor/${ this . sessionID } /` ) ;
95179 }
96180 }
97181
0 commit comments