@microsoft/windows-admin-center-sdk
Version:
Microsoft - Windows Admin Center Shell
320 lines (318 loc) • 15.8 kB
JavaScript
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