UNPKG

@microsoft/windows-admin-center-sdk

Version:

Microsoft - Windows Admin Center Shell

320 lines (318 loc) 15.8 kB
import { forkJoin, from, of, throwError } from 'rxjs'; import { catchError, switchMap } from 'rxjs/operators'; import { Net } from '../data/net'; import { PowerShell } from '../data/powershell'; import { LogLevel } from '../diagnostics/log-level'; import { Logging } from '../diagnostics/logging'; import { SmeWebTelemetry } from '../diagnostics/sme-web-telemetry'; import { TelemetryEventStates } from '../diagnostics/sme-web-telemetry-models'; import { EnvironmentModule } from '../manifest/environment-modules'; import { RpcWorkItemSubjectServer } from '../rpc/work-item/rpc-work-item-subject-server'; import { NotificationState } from './notification-state'; import { PowerShellNotification } from './powershell-notification'; import { WorkItemRequestType } from './work-item-request'; /** * Work item manager class. */ export class WorkItemManager { rpc; gatewayConnection; nodeConnection; notificationConnection; static apiWorkItems24hours = '/workitems?lastMinutes=1440'; static apiNotificationMessageStored = '/NotificationMessage/stored'; static apiNotificationMessageSubscriptionId = '/NotificationMessage/id'; active = false; startSubscription; powerShellNotification; notificationSubscription; rpcWorkItemSubscription; notificationManager; sequenceCounter = 1; sequencePackets = {}; rpcWorkItemSubjectServer; rootSubscriptionId; /** * Initializes a new instance of the WorkItemManager class. * * @param rpc the RPC object. * @param gatewayConnection the gateway connection service. * @param nodeConnection the node connection service. * @param notificationManager the notification manager. */ constructor(rpc, gatewayConnection, nodeConnection, notificationConnection) { this.rpc = rpc; this.gatewayConnection = gatewayConnection; this.nodeConnection = nodeConnection; this.notificationConnection = notificationConnection; this.notificationManager = this.notificationConnection.notificationManager; this.start(); } /** * Start the work item management. */ start() { this.stop(); this.rpcWorkItemSubjectServer = new RpcWorkItemSubjectServer(this.rpc); this.active = true; // pickup active work items for last 24hours this.startSubscription = this.gatewayConnection.get(WorkItemManager.apiWorkItems24hours) .pipe(catchError((error) => { const message = MsftSme.getStrings().MsftSmeShell.Core.Error.NotificationNoWorkItemFound.message; Logging.logError('Notification', message.format(Net.getErrorMessage(error))); return of({ response: { value: null } }); }), switchMap(response => { if (response.value) { response.value.forEach(element => this.notificationManager.addFromRecover(element)); } // start websocket status query. this.powerShellNotification = new PowerShellNotification(this.gatewayConnection.gatewayUrl); return from(this.powerShellNotification.initialize()); })) .subscribe({ next: () => { // notification from the gateway... this.notificationSubscription = this.powerShellNotification.subject .subscribe(item => { const message = item.message; if (message && message.sessionId) { // PowerShell Work Item message. const workItem = message; const psSessionId = workItem.sessionId; // Pass in psSessionId so telemetry can search up the PSCommand for this if (PowerShellNotification.hasError(item)) { SmeWebTelemetry.tracePowershellEvent(null, TelemetryEventStates.Error, { response: workItem, id: psSessionId }); } else if (PowerShellNotification.hasCompleted(item)) { SmeWebTelemetry.removePowershellId(psSessionId); } if (!this.notificationManager.updateFromMessage(psSessionId, item)) { const unexpectedMessage = MsftSme.getStrings().MsftSmeShell.Core.Error.NotificationUnexpectedReceived.message; Logging.log({ source: 'Notification', level: LogLevel.Warning, message: unexpectedMessage.format(psSessionId) }); } } else if (message && message.id) { // PowerShell Work Item message. const notification = message; const state = this.getErrorLevel(notification); // Pass in psSessionId so telemetry can search up the PSCommand for this if (state === NotificationState.Error) { SmeWebTelemetry.tracePowershellEvent(null, TelemetryEventStates.Error, { response: message, id: message.id }); } else if (state === NotificationState.Success) { SmeWebTelemetry.removePowershellId(message.id); } if (this.notificationManager.addForNotificationMessage(state, notification)) { this.powerShellNotification.subscribeSession(notification.id); } if (!this.notificationManager.updateFromNotificationMessage(state, message)) { const unexpectedMessage = MsftSme.getStrings().MsftSmeShell.Core.Error.NotificationUnexpectedReceived.message; Logging.logWarning('Notification', unexpectedMessage.format(message.id)); } } }); if (EnvironmentModule.isGatewayV200) { this.notificationSubscription.add(forkJoin([ this.gatewayConnection.get(WorkItemManager.apiNotificationMessageSubscriptionId), this.gatewayConnection.get(WorkItemManager.apiNotificationMessageStored) ]).subscribe(([idData, storedData]) => { this.rootSubscriptionId = idData.id; this.powerShellNotification.subscribeSession(this.rootSubscriptionId); storedData.value.forEach(element => { const state = this.getErrorLevel(element); this.notificationManager.addForNotificationMessage(state, element); this.powerShellNotification.subscribeSession(element.id); }); })); } // workItem request from rpc... this.rpcWorkItemSubscription = this.rpcWorkItemSubjectServer.subject .subscribe(item => { switch (item.data.type) { // legacy support for quick submission. it was expected 3 seconds response. case WorkItemRequestType.PowerShellSubmit: this.submitWorkItem(item.data).toPromise().then(item.deferred.resolve, item.deferred.reject); break; // new support for longer submission case like manage-as dialog displays. case WorkItemRequestType.WorkItemSubmit: const submitSequenceId = this.sequenceCounter++; item.data.sequenceId = submitSequenceId; this.submitWorkItem(item.data) .subscribe({ next: data => this.sequencePackets[submitSequenceId] = { success: true, data: data }, error: error => this.sequencePackets[submitSequenceId] = { success: false, data: error } }); // using sequence id, submission return immediately. item.deferred.resolve({ sequenceId: submitSequenceId, id: null }); break; // query to notification by 'id' or query to submission result by 'sequenceId'. case WorkItemRequestType.StateQuery: default: if (item.data.id) { // query to 'id' this.queryWorkItem(item.data).toPromise().then(item.deferred.resolve, item.deferred.reject); } else { // query to 'sequenceId' const querySequenceId = item.data.sequenceId; const result = this.sequencePackets[querySequenceId]; if (result) { // have a result, delete it and pass back as response. delete this.sequencePackets[querySequenceId]; if (result.success) { item.deferred.resolve(result.data); } else { item.deferred.reject(result.data); } } else { // no result yet. item.deferred.resolve({ sequenceId: querySequenceId, id: null }); } } break; } }); // subscribe the session notification to the gateway... this.notificationManager.items.forEach((value) => { // only if it's not finalized. if (value.state === NotificationState.Started || value.state === NotificationState.InProgress) { this.powerShellNotification.subscribeSession(value.id); } }); }, error: error => { const message = MsftSme.getStrings().MsftSmeShell.Core.Error.NotificationWebsocketInitialize.message; Logging.log({ source: 'Notification', level: LogLevel.Error, message: message.format(Net.getErrorMessage(error)) }); } }); } /** * Stop the work item management. */ stop() { this.active = false; if (this.startSubscription) { this.startSubscription.unsubscribe(); this.startSubscription = null; } if (this.notificationSubscription) { this.notificationSubscription.unsubscribe(); this.notificationSubscription = null; } if (this.powerShellNotification) { this.powerShellNotification.uninitialize(); this.powerShellNotification = null; } if (this.rpcWorkItemSubscription) { this.rpcWorkItemSubscription.unsubscribe(); this.rpcWorkItemSubscription = null; } } /** * Create and submit a workItem. * * @param request the work item request. * @return Observable the WorkItemResult observable. */ submitWorkItem(request) { let command = null; const notificationId = request.id || MsftSme.newGuid(); if (request.powerShellCommand || request.powerShellScript) { // For a non-powershell long running task (eg: azure site recovery setup), command is null. command = PowerShell.getPowerShellCommand(request.powerShellCommand || request.powerShellScript); } if (request.powerShellScript) { delete request['powerShellScript']; } if (request.powerShellCommand) { delete request['powerShellCommand']; } const nodeRequestOptions = PowerShell.newEndpointOptions(request.nodeRequestOptions); if (request.nodeRequestOptions) { delete request['nodeRequestOptions']; } // remember current URL where original request generated. request.locationPathname = window.location.pathname; request.locationSearch = window.location.search; // send submit notification this.notificationManager.addFromWorkItem(notificationId, request, request.startedMessage ? NotificationState.Started : NotificationState.InProgress); return this.powerShellNotification.submit(this.nodeConnection, request.nodeName, command, request, nodeRequestOptions, result => { if (!this.notificationManager.updateWorkItemWithPsSession(notificationId, result.id, request, result.state, result.error)) { const message = MsftSme.getStrings() .MsftSmeShell.Core.Error.NotificationUnexpectedReceived.message; Logging.log({ source: 'Notification', level: LogLevel.Warning, message: message.format(notificationId) }); } }); } /** * Query a workItem. * * @param request the work item request. * @return Observable the WorkItemResult observable. */ queryWorkItem(request) { const notification = this.notificationManager.find(request.id); if (notification) { if (notification.state === NotificationState.Error) { return of({ id: notification.id, state: notification.state, percent: notification.progressPercent, error: notification.object }); } return of({ id: notification.id, state: notification.state, percent: notification.progressPercent, object: notification.object }); } const message = MsftSme.getStrings().MsftSmeShell.Core.Error.NotificationNoIdFound.message; return throwError(() => message.format(request.id)); } getErrorLevel(notification) { switch (notification.errorLevel) { // Notification is unknown. case 0: case 'None': return NotificationState.Informational; // Notification is in progress. (non-terminated message.) case 1: case 'InProgress': return NotificationState.InProgress; // Notification is success. (terminated message.) case 2: case 'Success': return NotificationState.Success; // Notification is informational. (terminated message.) case 3: case 'Informational': return NotificationState.Informational; // Notification is warning. (terminated message.) case 4: case 'Warning': return NotificationState.Warning; // Notification is error. (terminated message.) case 5: case 'Error': return NotificationState.Error; // Notification is unknown. default: throw new Error('Couldn\'t get expected error level from a message notification.'); } } } //# sourceMappingURL=work-item-manager.js.map