mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
182
src/sql/parts/disasterRecovery/backup/backup.component.html
Normal file
182
src/sql/parts/disasterRecovery/backup/backup.component.html
Normal file
@@ -0,0 +1,182 @@
|
||||
<!--
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
<form class="angular-form" #myForm="ngForm" (ngSubmit)="onSubmit(f)">
|
||||
<div class="angular-modal-body" style="display: flex; flex-direction: column;">
|
||||
<div class="angular-modal-body-content">
|
||||
<div class="dialog-label">
|
||||
{{backupNameLabel}}
|
||||
</div>
|
||||
<div class="input-divider" #backupsetName>
|
||||
</div>
|
||||
<div class="dialog-label">
|
||||
{{recoveryModelLabel}}
|
||||
</div>
|
||||
<div class="input-divider" #recoveryModelContainer>
|
||||
</div>
|
||||
<div class="dialog-label">
|
||||
{{backupTypeLabel}}
|
||||
</div>
|
||||
<div class="input-divider" #backupTypeContainer>
|
||||
</div>
|
||||
<div class="input-divider" #copyOnlyContainer>{{copyOnlyLabel}}
|
||||
</div>
|
||||
<div class="dialog-label">
|
||||
{{backupDeviceLabel}}
|
||||
</div>
|
||||
<div class="backup-path-list">
|
||||
<div #pathContainer>
|
||||
</div>
|
||||
</div>
|
||||
<table class="backup-path-table">
|
||||
<tr>
|
||||
<td>
|
||||
<div class="backup-path-button" #addPathContainer></div>
|
||||
</td>
|
||||
<td style="padding-right:0px">
|
||||
<div class="backup-path-button" #removePathContainer></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Advanced Configuration -->
|
||||
<div class="monaco-split-view vertical advanced-main-header">
|
||||
<div class="split-view-view" >
|
||||
<!-- TODO: should this be shown in a new page instead of collapsible view? -->
|
||||
<div class="header collapsible collapsed" name="advanced-title" aria-expanded="false" style="height: 22px; background-color: rgba(128, 128, 128, 0.2);" (click)="onAdvancedClick()" #advancedHeader>
|
||||
<div class="title">{{advancedConfigurationLabel}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="body collapsible" name="advanced-body" style="height: calc(100% - 22px); display: none" #advancedBody>
|
||||
<!-- Compression -->
|
||||
<div class="dialog-label advanced-header" style="padding-top: 15px;">
|
||||
{{compressionLabel}}
|
||||
</div>
|
||||
<div class="indent">
|
||||
<div class="dialog-label">
|
||||
{{setBackupCompressionLabel}}
|
||||
</div>
|
||||
<div class="dialog-label" #compressionContainer>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Encryption -->
|
||||
<div class="dialog-label advanced-header">
|
||||
{{encryptionLabel}}
|
||||
</div>
|
||||
<div class="indent">
|
||||
<div class="option" #encryptCheckContainer>Encrypt backup
|
||||
</div>
|
||||
<div class="option" #encryptWarningContainer>
|
||||
<div class="icon warning">
|
||||
</div>
|
||||
<div class="warning-message">
|
||||
{{noEncryptorWarning}}
|
||||
</div>
|
||||
</div>
|
||||
<div #encryptContainer>
|
||||
<div class="dialog-label">
|
||||
{{algorithmLabel}}
|
||||
</div>
|
||||
<div class="dialog-label" #algorithmContainer>
|
||||
</div>
|
||||
<div class="dialog-label">
|
||||
{{certificateOrAsymmetricKeyLabel}}
|
||||
</div>
|
||||
<div class="dialog-label" #encryptorContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Overwrite media -->
|
||||
<div class="dialog-label advanced-header">
|
||||
{{mediaLabel}}
|
||||
</div>
|
||||
<div class="radio-indent">
|
||||
<div class="option">
|
||||
<input type="radio" name=media-option value="no_format" [checked]="!isFormatChecked" (change)="onChangeMediaFormat()" [disabled]="isEncryptChecked">{{mediaOptionLabel}}
|
||||
</div>
|
||||
<div style="margin-left:15px">
|
||||
<div class="option">
|
||||
<input type="radio" name=existing-media value="append" [(ngModel)]="selectedInitOption" [disabled]="isFormatChecked">{{existingMediaAppendLabel}}
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" name=existing-media value="overwrite" [(ngModel)]="selectedInitOption" [disabled]="isFormatChecked">{{existingMediaOverwriteLabel}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option">
|
||||
<input type="radio" name=media-option value="format" [checked]="isFormatChecked" (change)="onChangeMediaFormat()">{{mediaOptionFormatLabel}}
|
||||
</div>
|
||||
<div style="margin-left: 22px">
|
||||
<div class="dialog-label">
|
||||
{{newMediaSetNameLabel}}
|
||||
</div>
|
||||
<div class="dialog-label" #mediaName>
|
||||
</div>
|
||||
<div class="dialog-label">
|
||||
{{newMediaSetDescriptionLabel}}
|
||||
</div>
|
||||
<div class="dialog-label" #mediaDescription>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Transaction log -->
|
||||
<div class="dialog-label advanced-header">
|
||||
{{transactionLogLabel}}
|
||||
</div>
|
||||
<div class="radio-indent">
|
||||
<div class="option">
|
||||
<input type="radio" name=t-log value="truncate" [checked]="isTruncateChecked" (change)="onChangeTlog()" [disabled]="disableTlog">Truncate the transaction log
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" name=t-log value="taillog" [checked]="isTaillogChecked" (change)="onChangeTlog()" [disabled]="disableTlog">Backup the tail of the log
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reliability -->
|
||||
<div class="dialog-label advanced-header">
|
||||
{{reliabilityLabel}}
|
||||
</div>
|
||||
<div class="indent">
|
||||
<div class="option" #checksumContainer>{{checksumContainerLabel}}
|
||||
</div>
|
||||
<div class="option" #verifyContainer>{{verifyContainerLabel}}
|
||||
</div>
|
||||
<div class="option" #continueOnErrorContainer>{{continueOnErrorContainerLabel}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Backup expiration -->
|
||||
<div class="dialog-label advanced-header">
|
||||
{{expirationLabel}}
|
||||
</div>
|
||||
<div class="indent">
|
||||
<div class="dialog-label">
|
||||
{{setBackupRetainDaysLabel}}
|
||||
</div>
|
||||
<div class="dialog-label">
|
||||
<div #backupDaysContainer></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer" #modalFooterContainer>
|
||||
<div class="icon in-progress" #inProgressContainer></div>
|
||||
<div class="right-footer">
|
||||
<div class="footer-button" #scriptButtonContainer>
|
||||
</div>
|
||||
<div class="footer-button" #backupButtonContainer>
|
||||
</div>
|
||||
<div class="footer-button" #cancelButtonContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
921
src/sql/parts/disasterRecovery/backup/backup.component.ts
Normal file
921
src/sql/parts/disasterRecovery/backup/backup.component.ts
Normal file
@@ -0,0 +1,921 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!sql/parts/disasterRecovery/backup/media/backupDialog';
|
||||
|
||||
import { ElementRef, Component, Inject, forwardRef, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { ListBox } from 'sql/base/browser/ui/listBox/listBox';
|
||||
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox';
|
||||
import * as DialogHelper from 'sql/base/browser/ui/modal/dialogHelper';
|
||||
import { ModalFooterStyle } from 'sql/base/browser/ui/modal/modal';
|
||||
import { attachListBoxStyler, attachInputBoxStyler, attachSelectBoxStyler } from 'sql/common/theme/styler';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import * as BackupConstants from 'sql/parts/disasterRecovery/backup/constants';
|
||||
import { IDisasterRecoveryService, IDisasterRecoveryUiService, TaskExecutionMode } from 'sql/parts/disasterRecovery/common/interfaces';
|
||||
import FileValidationConstants = require('sql/parts/fileBrowser/common/fileValidationServiceConstants');
|
||||
import { FileBrowserDialog } from 'sql/parts/fileBrowser/fileBrowserDialog';
|
||||
import { DashboardComponentParams } from 'sql/services/bootstrap/bootstrapParams';
|
||||
import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import * as lifecycle from 'vs/base/common/lifecycle';
|
||||
import { attachButtonStyler, attachCheckboxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { localize } from 'vs/nls';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as types from 'vs/base/common/types';
|
||||
|
||||
export const BACKUP_SELECTOR: string = 'backup-component';
|
||||
|
||||
export class RestoreItemSource {
|
||||
restoreItemLocation: string;
|
||||
restoreItemDeviceType: number;
|
||||
isLogicalDevice: boolean;
|
||||
|
||||
constructor(location: any) {
|
||||
this.restoreItemDeviceType = location.restoreItemDeviceType;
|
||||
this.restoreItemLocation = location.restoreItemLocation;
|
||||
this.isLogicalDevice = location.isLogicalDevice;
|
||||
}
|
||||
}
|
||||
|
||||
interface MssqlBackupInfo {
|
||||
ownerUri: string;
|
||||
databaseName: string;
|
||||
backupType: number;
|
||||
backupComponent: number;
|
||||
backupDeviceType: number;
|
||||
selectedFiles: string;
|
||||
backupsetName: string;
|
||||
selectedFileGroup: { [path: string]: string };
|
||||
|
||||
// List of {key: backup path, value: device type}
|
||||
backupPathDevices: { [path: string]: number };
|
||||
backupPathList: [string];
|
||||
isCopyOnly: boolean;
|
||||
formatMedia: boolean;
|
||||
initialize: boolean;
|
||||
skipTapeHeader: boolean;
|
||||
mediaName: string;
|
||||
mediaDescription: string;
|
||||
checksum: boolean;
|
||||
continueAfterError: boolean;
|
||||
logTruncation: boolean;
|
||||
tailLogBackup: boolean;
|
||||
retainDays: number;
|
||||
compressionOption: number;
|
||||
verifyBackupRequired: boolean;
|
||||
encryptionAlgorithm: number;
|
||||
encryptorType: number;
|
||||
encryptorName: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: BACKUP_SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('sql/parts/disasterRecovery/backup/backup.component.html'))
|
||||
})
|
||||
export class BackupComponent {
|
||||
@ViewChild('pathContainer', { read: ElementRef }) pathElement;
|
||||
@ViewChild('backupTypeContainer', { read: ElementRef }) backupTypeElement;
|
||||
@ViewChild('backupsetName', { read: ElementRef }) backupNameElement;
|
||||
@ViewChild('compressionContainer', { read: ElementRef }) compressionElement;
|
||||
@ViewChild('tlogOption', { read: ElementRef }) tlogOptionElement;
|
||||
@ViewChild('algorithmContainer', { read: ElementRef }) encryptionAlgorithmElement;
|
||||
@ViewChild('encryptorContainer', { read: ElementRef }) encryptorElement;
|
||||
@ViewChild('mediaName', { read: ElementRef }) mediaNameElement;
|
||||
@ViewChild('mediaDescription', { read: ElementRef }) mediaDescriptionElement;
|
||||
@ViewChild('advancedBody', { read: ElementRef }) advancedBodyElement;
|
||||
@ViewChild('advancedHeader', { read: ElementRef }) advancedHeaderElement;
|
||||
@ViewChild('recoveryModelContainer', { read: ElementRef }) recoveryModelElement;
|
||||
@ViewChild('backupDaysContainer', { read: ElementRef }) backupDaysElement;
|
||||
@ViewChild('backupButtonContainer', { read: ElementRef }) backupButtonElement;
|
||||
@ViewChild('cancelButtonContainer', { read: ElementRef }) cancelButtonElement;
|
||||
@ViewChild('addPathContainer', { read: ElementRef }) addPathElement;
|
||||
@ViewChild('removePathContainer', { read: ElementRef }) removePathElement;
|
||||
@ViewChild('copyOnlyContainer', { read: ElementRef }) copyOnlyElement;
|
||||
@ViewChild('encryptCheckContainer', { read: ElementRef }) encryptElement;
|
||||
@ViewChild('encryptContainer', { read: ElementRef }) encryptContainerElement;
|
||||
@ViewChild('verifyContainer', { read: ElementRef }) verifyElement;
|
||||
@ViewChild('checksumContainer', { read: ElementRef }) checksumElement;
|
||||
@ViewChild('continueOnErrorContainer', { read: ElementRef }) continueOnErrorElement;
|
||||
@ViewChild('encryptWarningContainer', { read: ElementRef }) encryptWarningElement;
|
||||
@ViewChild('inProgressContainer', { read: ElementRef }) inProgressElement;
|
||||
@ViewChild('modalFooterContainer', { read: ElementRef }) modalFooterElement;
|
||||
@ViewChild('scriptButtonContainer', { read: ElementRef }) scriptButtonElement;
|
||||
|
||||
// tslint:disable:no-unused-variable
|
||||
private readonly backupNameLabel: string = localize('backup.backupName', 'Backup name');
|
||||
private readonly recoveryModelLabel: string = localize('backup.recoveryModel', 'Recovery model');
|
||||
private readonly backupTypeLabel: string = localize('backup.backupType', 'Backup type');
|
||||
private readonly backupDeviceLabel: string = localize('backup.backupDevice', 'Backup files');
|
||||
private readonly algorithmLabel: string = localize('backup.algorithm', 'Algorithm');
|
||||
private readonly certificateOrAsymmetricKeyLabel: string = localize('backup.certificateOrAsymmetricKey', 'Certificate or Asymmetric key');
|
||||
private readonly mediaLabel: string = localize('backup.media', 'Media');
|
||||
private readonly mediaOptionLabel: string = localize('backup.mediaOption', 'Backup to the existing media set');
|
||||
private readonly mediaOptionFormatLabel: string = localize('backup.mediaOptionFormat', 'Backup to a new media set');
|
||||
private readonly existingMediaAppendLabel: string = localize('backup.existingMediaAppend', 'Append to the existing backup set');
|
||||
private readonly existingMediaOverwriteLabel: string = localize('backup.existingMediaOverwrite', 'Overwrite all existing backup sets');
|
||||
private readonly newMediaSetNameLabel: string = localize('backup.newMediaSetName', 'New media set name');
|
||||
private readonly newMediaSetDescriptionLabel: string = localize('backup.newMediaSetDescription', 'New media set description');
|
||||
private readonly checksumContainerLabel: string = localize('backup.checksumContainer', 'Perform checksum before writing to media');
|
||||
private readonly verifyContainerLabel: string = localize('backup.verifyContainer', 'Verify backup when finished');
|
||||
private readonly continueOnErrorContainerLabel: string = localize('backup.continueOnErrorContainer', 'Continue on error');
|
||||
private readonly expirationLabel: string = localize('backup.expiration', 'Expiration');
|
||||
private readonly setBackupRetainDaysLabel: string = localize('backup.setBackupRetainDays', 'Set backup retain days');
|
||||
private readonly copyOnlyLabel: string = localize('backup.copyOnly', 'Copy-only backup');
|
||||
private readonly advancedConfigurationLabel: string = localize('backup.advancedConfiguration', 'Advanced Configuration');
|
||||
private readonly compressionLabel: string = localize('backup.compression', 'Compression');
|
||||
private readonly setBackupCompressionLabel: string = localize('backup.setBackupCompression', 'Set backup compression');
|
||||
private readonly encryptionLabel: string = localize('backup.encryption', 'Encryption');
|
||||
private readonly transactionLogLabel: string = localize('backup.transactionLog', 'Transaction log');
|
||||
private readonly reliabilityLabel: string = localize('backup.reliability', 'Reliability');
|
||||
private readonly mediaNameRequiredError: string = localize('backup.mediaNameRequired', 'Media name is required');
|
||||
private readonly noEncryptorWarning: string = localize('backup.noEncryptorWarning', "No certificate or asymmetric key is available");
|
||||
|
||||
// tslint:enable:no-unused-variable
|
||||
|
||||
private _disasterRecoveryService: IDisasterRecoveryService;
|
||||
private _disasterRecoveryUiService: IDisasterRecoveryUiService;
|
||||
private _uri: string;
|
||||
private _toDispose: lifecycle.IDisposable[] = [];
|
||||
|
||||
private connection: IConnectionProfile;
|
||||
private databaseName: string;
|
||||
private defaultNewBackupFolder: string;
|
||||
private recoveryModel: string;
|
||||
private backupEncryptors;
|
||||
private containsBackupToUrl: boolean;
|
||||
private fileBrowserDialog: FileBrowserDialog;
|
||||
private errorMessage: string;
|
||||
|
||||
// UI element disable flag
|
||||
private disableFileComponent: boolean;
|
||||
private disableTlog: boolean;
|
||||
|
||||
private selectedBackupComponent: string;
|
||||
private selectedFilesText: string;
|
||||
private selectedInitOption: string;
|
||||
private isTruncateChecked: boolean;
|
||||
private isTaillogChecked: boolean;
|
||||
private isFormatChecked: boolean;
|
||||
private isEncryptChecked: boolean;
|
||||
// Key: backup path, Value: device type
|
||||
private backupPathTypePairs: { [path: string]: number };
|
||||
|
||||
private compressionOptions = [BackupConstants.defaultCompression, BackupConstants.compressionOn, BackupConstants.compressionOff];
|
||||
private encryptionAlgorithms = [BackupConstants.aes128, BackupConstants.aes192, BackupConstants.aes256, BackupConstants.tripleDES];
|
||||
private existingMediaOptions = ['append', 'overwrite'];
|
||||
private backupTypeOptions: string[];
|
||||
|
||||
private backupTypeSelectBox: SelectBox;
|
||||
private backupNameBox: InputBox;
|
||||
private recoveryBox: InputBox;
|
||||
private backupRetainDaysBox: InputBox;
|
||||
private backupButton: Button;
|
||||
private cancelButton: Button;
|
||||
private scriptButton: Button;
|
||||
private addPathButton: Button;
|
||||
private removePathButton: Button;
|
||||
private pathListBox: ListBox;
|
||||
private compressionSelectBox: SelectBox;
|
||||
private algorithmSelectBox: SelectBox;
|
||||
private encryptorSelectBox: SelectBox;
|
||||
private mediaNameBox: InputBox;
|
||||
private mediaDescriptionBox: InputBox;
|
||||
private copyOnlyCheckBox: Checkbox;
|
||||
private encryptCheckBox: Checkbox;
|
||||
private verifyCheckBox: Checkbox;
|
||||
private checksumCheckBox: Checkbox;
|
||||
private continueOnErrorCheckBox: Checkbox;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeDetectorRef: ChangeDetectorRef,
|
||||
@Inject(BOOTSTRAP_SERVICE_ID) private _bootstrapService: IBootstrapService,
|
||||
) {
|
||||
this._disasterRecoveryService = _bootstrapService.disasterRecoveryService;
|
||||
this._disasterRecoveryUiService = _bootstrapService.disasterRecoveryUiService;
|
||||
this._disasterRecoveryUiService.onShowBackupEvent((param) => this.onGetBackupConfigInfo(param));
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
let self = this;
|
||||
|
||||
this.addFooterButtons();
|
||||
|
||||
this.recoveryBox = new InputBox(this.recoveryModelElement.nativeElement, this._bootstrapService.contextViewService, { placeholder: this.recoveryModel });
|
||||
// Set backup type
|
||||
this.backupTypeSelectBox = new SelectBox([], '');
|
||||
this.backupTypeSelectBox.render(this.backupTypeElement.nativeElement);
|
||||
|
||||
// Set copy-only check box
|
||||
this.copyOnlyCheckBox = new Checkbox({
|
||||
actionClassName: 'backup-checkbox',
|
||||
title: this.copyOnlyLabel,
|
||||
isChecked: false,
|
||||
onChange: (viaKeyboard) => { }
|
||||
});
|
||||
|
||||
this.copyOnlyElement.nativeElement.appendChild(this.copyOnlyCheckBox.domNode);
|
||||
|
||||
// Encryption checkbox
|
||||
this.encryptCheckBox = new Checkbox({
|
||||
actionClassName: 'backup-checkbox',
|
||||
title: 'Encryption',
|
||||
isChecked: false,
|
||||
onChange: (viaKeyboard) => self.onChangeEncrypt()
|
||||
});
|
||||
this.encryptElement.nativeElement.appendChild(this.encryptCheckBox.domNode);
|
||||
|
||||
// Verify backup checkbox
|
||||
this.verifyCheckBox = new Checkbox({
|
||||
actionClassName: 'backup-checkbox',
|
||||
title: 'Verify',
|
||||
isChecked: false,
|
||||
onChange: (viaKeyboard) => { }
|
||||
});
|
||||
this.verifyElement.nativeElement.appendChild(this.verifyCheckBox.domNode);
|
||||
|
||||
// Perform checksum checkbox
|
||||
this.checksumCheckBox = new Checkbox({
|
||||
actionClassName: 'backup-checkbox',
|
||||
title: 'Perform checksum',
|
||||
isChecked: false,
|
||||
onChange: (viaKeyboard) => { }
|
||||
});
|
||||
this.checksumElement.nativeElement.appendChild(this.checksumCheckBox.domNode);
|
||||
|
||||
// Continue on error checkbox
|
||||
this.continueOnErrorCheckBox = new Checkbox({
|
||||
actionClassName: 'backup-checkbox',
|
||||
title: 'Continue on error',
|
||||
isChecked: false,
|
||||
onChange: (viaKeyboard) => { }
|
||||
});
|
||||
this.continueOnErrorElement.nativeElement.appendChild(this.continueOnErrorCheckBox.domNode);
|
||||
|
||||
// Set backup name
|
||||
this.backupNameBox = new InputBox(this.backupNameElement.nativeElement, this._bootstrapService.contextViewService);
|
||||
this.backupNameBox.focus();
|
||||
|
||||
// Set backup path list
|
||||
this.pathListBox = new ListBox([], '', this._bootstrapService.contextViewService);
|
||||
this.pathListBox.render(this.pathElement.nativeElement);
|
||||
|
||||
// Set backup path add/remove buttons
|
||||
this.addPathButton = new Button(this.addPathElement.nativeElement);
|
||||
this.addPathButton.label = '+';
|
||||
|
||||
this.removePathButton = new Button(this.removePathElement.nativeElement);
|
||||
this.removePathButton.label = '-';
|
||||
|
||||
// Set compression
|
||||
this.compressionSelectBox = new SelectBox(this.compressionOptions, this.compressionOptions[0]);
|
||||
this.compressionSelectBox.render(this.compressionElement.nativeElement);
|
||||
|
||||
// Set encryption
|
||||
this.algorithmSelectBox = new SelectBox(this.encryptionAlgorithms, this.encryptionAlgorithms[0]);
|
||||
this.algorithmSelectBox.render(this.encryptionAlgorithmElement.nativeElement);
|
||||
this.encryptorSelectBox = new SelectBox([], '');
|
||||
this.encryptorSelectBox.render(this.encryptorElement.nativeElement);
|
||||
|
||||
// Set media
|
||||
this.mediaNameBox = new InputBox(this.mediaNameElement.nativeElement,
|
||||
this._bootstrapService.contextViewService,
|
||||
{
|
||||
validationOptions: {
|
||||
validation: (value: string) => !value ? ({ type: MessageType.ERROR, content: this.mediaNameRequiredError }) : null
|
||||
}
|
||||
});
|
||||
|
||||
this.mediaDescriptionBox = new InputBox(this.mediaDescriptionElement.nativeElement, this._bootstrapService.contextViewService);
|
||||
|
||||
// Set backup retain days
|
||||
let invalidInputMessage = localize('invalidInput', 'Invalid input. Value must be greater than or equal 0.');
|
||||
this.backupRetainDaysBox = new InputBox(this.backupDaysElement.nativeElement,
|
||||
this._bootstrapService.contextViewService,
|
||||
{
|
||||
placeholder: '0',
|
||||
type: 'number',
|
||||
min: '0',
|
||||
validationOptions: {
|
||||
validation: (value: string) => {
|
||||
if (types.isNumber(Number(value)) && Number(value) < 0) {
|
||||
return { type: MessageType.ERROR, content: invalidInputMessage };
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// disable elements
|
||||
this.recoveryBox.disable();
|
||||
this.mediaNameBox.disable();
|
||||
this.mediaDescriptionBox.disable();
|
||||
|
||||
this.registerListeners();
|
||||
this.updateTheme();
|
||||
}
|
||||
|
||||
private onGetBackupConfigInfo(param: DashboardComponentParams) {
|
||||
// Show spinner
|
||||
this.showSpinner();
|
||||
this.backupEnabled = false;
|
||||
|
||||
// Reset backup values
|
||||
this.backupNameBox.value = '';
|
||||
this.pathListBox.setOptions([], 0);
|
||||
|
||||
this.connection = param.connection;
|
||||
this._uri = param.ownerUri;
|
||||
|
||||
// Get backup configuration info
|
||||
this._disasterRecoveryService.getBackupConfigInfo(this._uri).then(configInfo => {
|
||||
if (configInfo) {
|
||||
this.defaultNewBackupFolder = configInfo.defaultBackupFolder;
|
||||
this.recoveryModel = configInfo.recoveryModel;
|
||||
this.backupEncryptors = configInfo.backupEncryptors;
|
||||
this.initialize(true);
|
||||
} else {
|
||||
this.initialize(false);
|
||||
}
|
||||
// Hide spinner
|
||||
this.hideSpinner();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show spinner in the backup dialog
|
||||
*/
|
||||
private showSpinner(): void {
|
||||
this.inProgressElement.nativeElement.style.visibility = 'visible';
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide spinner in the backup dialog
|
||||
*/
|
||||
private hideSpinner(): void {
|
||||
this.inProgressElement.nativeElement.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
private addFooterButtons(): void {
|
||||
// Set script footer button
|
||||
this.scriptButton = new Button(this.scriptButtonElement.nativeElement);
|
||||
this.scriptButton.label = localize('script', 'Script');
|
||||
this._toDispose.push(this.addButtonClickHandler(this.scriptButton, () => this.onScript()));
|
||||
this._toDispose.push(attachButtonStyler(this.scriptButton, this._bootstrapService.themeService));
|
||||
this.scriptButton.enabled = false;
|
||||
|
||||
// Set backup footer button
|
||||
this.backupButton = new Button(this.backupButtonElement.nativeElement);
|
||||
this.backupButton.label = localize('backup', 'Backup');
|
||||
this._toDispose.push(this.addButtonClickHandler(this.backupButton, () => this.onOk()));
|
||||
|
||||
this._toDispose.push(attachButtonStyler(this.backupButton, this._bootstrapService.themeService));
|
||||
this.backupEnabled = false;
|
||||
|
||||
// Set cancel footer button
|
||||
this.cancelButton = new Button(this.cancelButtonElement.nativeElement);
|
||||
this.cancelButton.label = localize('cancel', 'Cancel');
|
||||
this._toDispose.push(this.addButtonClickHandler(this.cancelButton, () => this.onCancel()));
|
||||
this._toDispose.push(attachButtonStyler(this.cancelButton, this._bootstrapService.themeService));
|
||||
}
|
||||
|
||||
private initialize(isMetadataPopulated: boolean): void {
|
||||
this.databaseName = this.connection.databaseName;
|
||||
this.selectedBackupComponent = BackupConstants.labelDatabase;
|
||||
this.backupPathTypePairs = {};
|
||||
this.isFormatChecked = false;
|
||||
this.isEncryptChecked = false;
|
||||
this.selectedInitOption = this.existingMediaOptions[0];
|
||||
this.backupTypeOptions = [];
|
||||
|
||||
if (isMetadataPopulated) {
|
||||
this.backupEnabled = true;
|
||||
|
||||
// Set recovery model
|
||||
this.setControlsForRecoveryModel();
|
||||
|
||||
// Set backup type
|
||||
this.backupTypeSelectBox.setOptions(this.backupTypeOptions, 0);
|
||||
|
||||
this.setDefaultBackupName();
|
||||
this.backupNameBox.focus();
|
||||
|
||||
// Set backup path list
|
||||
this.setDefaultBackupPaths();
|
||||
var pathlist = [];
|
||||
for (var i in this.backupPathTypePairs) {
|
||||
pathlist.push(i);
|
||||
}
|
||||
this.pathListBox.setOptions(pathlist, 0);
|
||||
|
||||
// Set encryption
|
||||
var encryptorItems = this.populateEncryptorCombo();
|
||||
this.encryptorSelectBox.setOptions(encryptorItems, 0);
|
||||
|
||||
if (encryptorItems.length === 0) {
|
||||
// Disable encryption checkbox
|
||||
this.encryptCheckBox.disable();
|
||||
|
||||
// Show warning instead of algorithm select boxes
|
||||
(<HTMLElement>this.encryptWarningElement.nativeElement).style.display = 'inline';
|
||||
(<HTMLElement>this.encryptContainerElement.nativeElement).style.display = 'none';
|
||||
}
|
||||
else {
|
||||
// Show algorithm select boxes instead of warning
|
||||
(<HTMLElement>this.encryptWarningElement.nativeElement).style.display = 'none';
|
||||
(<HTMLElement>this.encryptContainerElement.nativeElement).style.display = 'inline';
|
||||
|
||||
// Disable the algorithm select boxes since encryption is not checked by default
|
||||
this.setEncryptOptionsEnabled(false);
|
||||
}
|
||||
|
||||
this.setTLogOptions();
|
||||
|
||||
// disable elements
|
||||
this.recoveryBox.disable();
|
||||
this.mediaNameBox.disable();
|
||||
this.mediaDescriptionBox.disable();
|
||||
this.recoveryBox.value = this.recoveryModel;
|
||||
|
||||
// show warning message if latest backup file path contains url
|
||||
if (this.containsBackupToUrl) {
|
||||
this.pathListBox.setValidation(false, { content: localize('backup.containsBackupToUrlError', 'Only backup to file is supported'), type: MessageType.WARNING });
|
||||
this.pathListBox.focus();
|
||||
}
|
||||
}
|
||||
|
||||
this._changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset dialog controls to their initial state.
|
||||
*/
|
||||
private resetDialog(): void {
|
||||
this.isFormatChecked = false;
|
||||
this.isEncryptChecked = false;
|
||||
|
||||
this.copyOnlyCheckBox.checked = false;
|
||||
this.copyOnlyCheckBox.enable();
|
||||
this.compressionSelectBox.setOptions(this.compressionOptions, 0);
|
||||
this.encryptCheckBox.checked = false;
|
||||
this.encryptCheckBox.enable();
|
||||
this.onChangeEncrypt();
|
||||
|
||||
this.mediaNameBox.value = '';
|
||||
this.mediaDescriptionBox.value = '';
|
||||
this.checksumCheckBox.checked = false;
|
||||
this.verifyCheckBox.checked = false;
|
||||
this.continueOnErrorCheckBox.checked = false;
|
||||
this.backupRetainDaysBox.value = '0';
|
||||
this.algorithmSelectBox.setOptions(this.encryptionAlgorithms, 0);
|
||||
this.selectedInitOption = this.existingMediaOptions[0];
|
||||
this.collapseAdvancedOptions();
|
||||
this.containsBackupToUrl = false;
|
||||
this.pathListBox.setValidation(true);
|
||||
|
||||
this.cancelButton.applyStyles();
|
||||
this.scriptButton.applyStyles();
|
||||
this.backupButton.applyStyles();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
// Theme styler
|
||||
this._toDispose.push(attachInputBoxStyler(this.backupNameBox, this._bootstrapService.themeService));
|
||||
this._toDispose.push(attachInputBoxStyler(this.recoveryBox, this._bootstrapService.themeService));
|
||||
this._toDispose.push(attachSelectBoxStyler(this.backupTypeSelectBox, this._bootstrapService.themeService));
|
||||
this._toDispose.push(attachListBoxStyler(this.pathListBox, this._bootstrapService.themeService));
|
||||
this._toDispose.push(attachButtonStyler(this.addPathButton, this._bootstrapService.themeService));
|
||||
this._toDispose.push(attachButtonStyler(this.removePathButton, this._bootstrapService.themeService));
|
||||
this._toDispose.push(attachSelectBoxStyler(this.compressionSelectBox, this._bootstrapService.themeService));
|
||||
this._toDispose.push(attachSelectBoxStyler(this.algorithmSelectBox, this._bootstrapService.themeService));
|
||||
this._toDispose.push(attachSelectBoxStyler(this.encryptorSelectBox, this._bootstrapService.themeService));
|
||||
this._toDispose.push(attachInputBoxStyler(this.mediaNameBox, this._bootstrapService.themeService));
|
||||
this._toDispose.push(attachInputBoxStyler(this.mediaDescriptionBox, this._bootstrapService.themeService));
|
||||
this._toDispose.push(attachInputBoxStyler(this.backupRetainDaysBox, this._bootstrapService.themeService));
|
||||
this._toDispose.push(attachCheckboxStyler(this.copyOnlyCheckBox, this._bootstrapService.themeService));
|
||||
this._toDispose.push(attachCheckboxStyler(this.encryptCheckBox, this._bootstrapService.themeService));
|
||||
this._toDispose.push(attachCheckboxStyler(this.verifyCheckBox, this._bootstrapService.themeService));
|
||||
this._toDispose.push(attachCheckboxStyler(this.checksumCheckBox, this._bootstrapService.themeService));
|
||||
this._toDispose.push(attachCheckboxStyler(this.continueOnErrorCheckBox, this._bootstrapService.themeService));
|
||||
|
||||
this._toDispose.push(this.backupTypeSelectBox.onDidSelect(selected => this.onBackupTypeChanged()));
|
||||
this._toDispose.push(this.addButtonClickHandler(this.addPathButton, () => this.onAddClick()));
|
||||
this._toDispose.push(this.addButtonClickHandler(this.removePathButton, () => this.onRemoveClick()));
|
||||
this._toDispose.push(this.mediaNameBox.onDidChange(mediaName => {
|
||||
this.mediaNameChanged(mediaName);
|
||||
}));
|
||||
this._toDispose.push(this.backupRetainDaysBox.onDidChange(days => {
|
||||
this.backupRetainDaysChanged(days);
|
||||
}));
|
||||
|
||||
this._toDispose.push(this._bootstrapService.themeService.onDidColorThemeChange(e => this.updateTheme()));
|
||||
}
|
||||
|
||||
// Update theming that is specific to backup dialog
|
||||
private updateTheme(): void {
|
||||
// set modal footer style
|
||||
let footerHtmlElement: HTMLElement = <HTMLElement>this.modalFooterElement.nativeElement;
|
||||
footerHtmlElement.style.backgroundColor = ModalFooterStyle.backgroundColor;
|
||||
footerHtmlElement.style.borderTopWidth = ModalFooterStyle.borderTopWidth;
|
||||
footerHtmlElement.style.borderTopStyle = ModalFooterStyle.borderTopStyle;
|
||||
footerHtmlElement.style.borderTopColor = ModalFooterStyle.borderTopColor;
|
||||
}
|
||||
|
||||
private addButtonClickHandler(button: Button, handler: () => void): lifecycle.IDisposable {
|
||||
if (button && handler) {
|
||||
return DOM.addDisposableListener(button.getElement(), DOM.EventType.CLICK, () => {
|
||||
if (button.enabled) {
|
||||
handler();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* UI event handlers
|
||||
*/
|
||||
private onScript(): void {
|
||||
this._disasterRecoveryService.backup(this._uri, this.createBackupInfo(), TaskExecutionMode.script);
|
||||
this.close();
|
||||
}
|
||||
|
||||
private onOk(): void {
|
||||
this._disasterRecoveryService.backup(this._uri, this.createBackupInfo(), TaskExecutionMode.executeAndScript);
|
||||
this.close();
|
||||
}
|
||||
|
||||
private onCancel(): void {
|
||||
this.close();
|
||||
this._bootstrapService.connectionManagementService.disconnect(this._uri);
|
||||
}
|
||||
|
||||
private close(): void {
|
||||
this._disasterRecoveryUiService.closeBackup();
|
||||
this.resetDialog();
|
||||
}
|
||||
|
||||
private onChangeTlog(): void {
|
||||
this.isTruncateChecked = !this.isTruncateChecked;
|
||||
this.isTaillogChecked = !this.isTaillogChecked;
|
||||
this.detectChange();
|
||||
}
|
||||
|
||||
private onChangeEncrypt(): void {
|
||||
if (this.encryptCheckBox.checked) {
|
||||
this.setEncryptOptionsEnabled(true);
|
||||
|
||||
// Force to choose format media option since otherwise encryption cannot be done
|
||||
if (!this.isFormatChecked) {
|
||||
this.onChangeMediaFormat();
|
||||
}
|
||||
} else {
|
||||
this.setEncryptOptionsEnabled(false);
|
||||
}
|
||||
this.isEncryptChecked = this.encryptCheckBox.checked;
|
||||
this.detectChange();
|
||||
}
|
||||
|
||||
private onChangeMediaFormat(): void {
|
||||
this.isFormatChecked = !this.isFormatChecked;
|
||||
this.enableMediaInput(this.isFormatChecked);
|
||||
if (this.isFormatChecked) {
|
||||
if (DialogHelper.isNullOrWhiteSpace(this.mediaNameBox.value)) {
|
||||
this.backupEnabled = false;
|
||||
this.backupButton.enabled = false;
|
||||
this.mediaNameBox.showMessage({ type: MessageType.ERROR, content: this.mediaNameRequiredError });
|
||||
}
|
||||
} else {
|
||||
this.enableBackupButton();
|
||||
}
|
||||
this.detectChange();
|
||||
}
|
||||
|
||||
private set backupEnabled(value: boolean) {
|
||||
this.backupButton.enabled = value;
|
||||
this.scriptButton.enabled = value;
|
||||
}
|
||||
|
||||
private onAdvancedClick(): void {
|
||||
if (this.advancedHeaderElement.nativeElement.style['aria-expanded']) {
|
||||
// collapse
|
||||
this.collapseAdvancedOptions();
|
||||
} else {
|
||||
// expand
|
||||
this.expandAdvancedOptions();
|
||||
}
|
||||
|
||||
this.detectChange();
|
||||
}
|
||||
|
||||
private collapseAdvancedOptions() {
|
||||
this.advancedHeaderElement.nativeElement.className = 'header collapsible collapsed';
|
||||
this.advancedBodyElement.nativeElement.style = 'display: none';
|
||||
this.advancedHeaderElement.nativeElement.style['aria-expanded'] = false;
|
||||
}
|
||||
|
||||
|
||||
private expandAdvancedOptions() {
|
||||
this.advancedHeaderElement.nativeElement.className = 'header collapsible';
|
||||
this.advancedBodyElement.nativeElement.style = 'display: inline';
|
||||
this.advancedHeaderElement.nativeElement.style['aria-expanded'] = true;
|
||||
}
|
||||
|
||||
private onBackupTypeChanged(): void {
|
||||
if (this.getSelectedBackupType() === BackupConstants.labelDifferential) {
|
||||
this.copyOnlyCheckBox.checked = false;
|
||||
this.copyOnlyCheckBox.disable();
|
||||
} else {
|
||||
this.copyOnlyCheckBox.enable();
|
||||
}
|
||||
|
||||
this.setTLogOptions();
|
||||
this.setDefaultBackupName();
|
||||
this._changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
private onAddClick(): void {
|
||||
this._bootstrapService.fileBrowserDialogService.showDialog(this._uri,
|
||||
this.defaultNewBackupFolder,
|
||||
BackupConstants.fileFiltersSet,
|
||||
FileValidationConstants.backup,
|
||||
false,
|
||||
(filepath => this.handlePathAdded(filepath)));
|
||||
}
|
||||
|
||||
private handlePathAdded(filepath: string) {
|
||||
if (filepath && !this.backupPathTypePairs[filepath]) {
|
||||
if ((this.getBackupPathCount() < BackupConstants.maxDevices)) {
|
||||
this.backupPathTypePairs[filepath] = BackupConstants.deviceTypeFile;
|
||||
this.pathListBox.add(filepath);
|
||||
this.enableBackupButton();
|
||||
this.enableAddRemoveButtons();
|
||||
|
||||
// stop showing error message if the list content was invalid due to no file path
|
||||
if (!this.pathListBox.isContentValid && this.pathListBox.count === 1) {
|
||||
this.pathListBox.setValidation(true);
|
||||
}
|
||||
|
||||
this._changeDetectorRef.detectChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onRemoveClick(): void {
|
||||
let self = this;
|
||||
this.pathListBox.selectedOptions.forEach(selected => {
|
||||
if (self.backupPathTypePairs[selected]) {
|
||||
if (self.backupPathTypePairs[selected] === BackupConstants.deviceTypeURL) {
|
||||
// stop showing warning message since url path is getting removed
|
||||
this.pathListBox.setValidation(true);
|
||||
this.containsBackupToUrl = false;
|
||||
}
|
||||
|
||||
delete self.backupPathTypePairs[selected];
|
||||
}
|
||||
});
|
||||
|
||||
this.pathListBox.remove();
|
||||
if (this.pathListBox.count === 0) {
|
||||
this.backupEnabled = false;
|
||||
|
||||
// show input validation error
|
||||
this.pathListBox.setValidation(false, { content: localize('backup.backupFileRequired', 'Backup file path is required'), type: MessageType.ERROR });
|
||||
this.pathListBox.focus();
|
||||
}
|
||||
|
||||
this.enableAddRemoveButtons();
|
||||
this._changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
private enableAddRemoveButtons(): void {
|
||||
if (this.pathListBox.count === 0) {
|
||||
this.removePathButton.enabled = false;
|
||||
} else if (this.pathListBox.count === BackupConstants.maxDevices) {
|
||||
this.addPathButton.enabled = false;
|
||||
} else {
|
||||
this.removePathButton.enabled = true;
|
||||
this.addPathButton.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper methods
|
||||
*/
|
||||
private setControlsForRecoveryModel(): void {
|
||||
if (this.recoveryModel === BackupConstants.recoveryModelSimple) {
|
||||
this.selectedBackupComponent = BackupConstants.labelDatabase;
|
||||
this.disableFileComponent = true;
|
||||
} else {
|
||||
this.disableFileComponent = false;
|
||||
}
|
||||
|
||||
this.populateBackupTypes();
|
||||
}
|
||||
|
||||
private populateBackupTypes(): void {
|
||||
this.backupTypeOptions.push(BackupConstants.labelFull);
|
||||
if (this.databaseName !== 'master') {
|
||||
this.backupTypeOptions.push(BackupConstants.labelDifferential);
|
||||
if (this.recoveryModel !== BackupConstants.recoveryModelSimple) {
|
||||
this.backupTypeOptions.push(BackupConstants.labelLog);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private populateEncryptorCombo(): string[] {
|
||||
var encryptorCombo = [];
|
||||
this.backupEncryptors.forEach((encryptor) => {
|
||||
var encryptorTypeStr = (encryptor.encryptorType === 0 ? BackupConstants.serverCertificate : BackupConstants.asymmetricKey);
|
||||
encryptorCombo.push(encryptor.encryptorName + '(' + encryptorTypeStr + ')');
|
||||
});
|
||||
return encryptorCombo;
|
||||
}
|
||||
|
||||
private setDefaultBackupName(): void {
|
||||
let utc = new Date().toJSON().slice(0, 19);
|
||||
if (this.backupNameBox) {
|
||||
this.backupNameBox.value = this.databaseName + '-' + this.getSelectedBackupType() + '-' + utc;
|
||||
}
|
||||
}
|
||||
|
||||
private setDefaultBackupPaths(): void {
|
||||
if (this.defaultNewBackupFolder && this.defaultNewBackupFolder.length > 0) {
|
||||
|
||||
// TEMPORARY WORKAROUND: karlb 5/27 - try to guess path separator on server based on first character in path
|
||||
let serverPathSeparator: string = '\\';
|
||||
if (this.defaultNewBackupFolder[0] === '/') {
|
||||
serverPathSeparator = '/';
|
||||
}
|
||||
let d: Date = new Date();
|
||||
let formattedDateTime: string = `-${d.getFullYear()}${d.getMonth() + 1}${d.getDate()}-${d.getHours()}-${d.getMinutes()}-${d.getSeconds()}`;
|
||||
let defaultNewBackupLocation = this.defaultNewBackupFolder + serverPathSeparator + this.databaseName + formattedDateTime + '.bak';
|
||||
|
||||
// Add a default new backup location
|
||||
this.backupPathTypePairs[defaultNewBackupLocation] = BackupConstants.deviceTypeFile;
|
||||
}
|
||||
}
|
||||
|
||||
private isBackupToFile(controllerType: number): boolean {
|
||||
let isfile = false;
|
||||
if (controllerType === 102) {
|
||||
isfile = true;
|
||||
} else if (controllerType === 105) {
|
||||
isfile = false;
|
||||
} else if (controllerType === BackupConstants.backupDeviceTypeDisk) {
|
||||
isfile = true;
|
||||
} else if (controllerType === BackupConstants.backupDeviceTypeTape || controllerType === BackupConstants.backupDeviceTypeURL) {
|
||||
isfile = false;
|
||||
}
|
||||
return isfile;
|
||||
}
|
||||
|
||||
private enableMediaInput(enable: boolean): void {
|
||||
if (enable) {
|
||||
this.mediaNameBox.enable();
|
||||
this.mediaDescriptionBox.enable();
|
||||
} else {
|
||||
this.mediaNameBox.disable();
|
||||
this.mediaDescriptionBox.disable();
|
||||
}
|
||||
}
|
||||
|
||||
private detectChange(): void {
|
||||
this._changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
private setTLogOptions(): void {
|
||||
if (this.getSelectedBackupType() === BackupConstants.labelLog) {
|
||||
// Enable log options
|
||||
this.disableTlog = false;
|
||||
// Choose the default option
|
||||
this.isTruncateChecked = true;
|
||||
} else {
|
||||
// Unselect log options
|
||||
this.isTruncateChecked = false;
|
||||
this.isTaillogChecked = false;
|
||||
// Disable log options
|
||||
this.disableTlog = true;
|
||||
}
|
||||
}
|
||||
|
||||
private getBackupTypeNumber(): number {
|
||||
let backupType;
|
||||
switch (this.getSelectedBackupType()) {
|
||||
case BackupConstants.labelFull:
|
||||
backupType = 0;
|
||||
break;
|
||||
case BackupConstants.labelDifferential:
|
||||
backupType = 1;
|
||||
break;
|
||||
case BackupConstants.labelLog:
|
||||
backupType = 2;
|
||||
break;
|
||||
}
|
||||
return backupType;
|
||||
}
|
||||
|
||||
private getBackupPathCount(): number {
|
||||
return this.pathListBox.count;
|
||||
}
|
||||
|
||||
private getSelectedBackupType(): string {
|
||||
let backupType = '';
|
||||
if (this.backupTypeSelectBox) {
|
||||
backupType = this.backupTypeSelectBox.value;
|
||||
}
|
||||
return backupType;
|
||||
}
|
||||
|
||||
private enableBackupButton(): void {
|
||||
if (!this.backupButton.enabled) {
|
||||
if (this.pathListBox.count > 0 && (!this.isFormatChecked || this.mediaNameBox.value) && this.backupRetainDaysBox.validate()) {
|
||||
this.backupEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setEncryptOptionsEnabled(enabled: boolean): void {
|
||||
if (enabled) {
|
||||
this.algorithmSelectBox.enable();
|
||||
this.encryptorSelectBox.enable();
|
||||
} else {
|
||||
this.algorithmSelectBox.disable();
|
||||
this.encryptorSelectBox.disable();
|
||||
}
|
||||
}
|
||||
|
||||
private mediaNameChanged(mediaName: string): void {
|
||||
if (!mediaName) {
|
||||
this.backupEnabled = false;
|
||||
} else {
|
||||
this.enableBackupButton();
|
||||
}
|
||||
}
|
||||
|
||||
private backupRetainDaysChanged(days: string): void {
|
||||
if (!this.backupRetainDaysBox.validate()) {
|
||||
this.backupEnabled = false;
|
||||
} else {
|
||||
this.enableBackupButton();
|
||||
}
|
||||
}
|
||||
|
||||
private createBackupInfo(): MssqlBackupInfo {
|
||||
var backupPathArray = [];
|
||||
for (var i in this.backupPathTypePairs) {
|
||||
backupPathArray.push(i);
|
||||
}
|
||||
|
||||
// get encryptor type and name
|
||||
var encryptorName = '';
|
||||
var encryptorType;
|
||||
|
||||
if (this.encryptCheckBox.checked && this.encryptorSelectBox.value !== '') {
|
||||
var selectedEncryptor = this.encryptorSelectBox.value;
|
||||
var encryptorTypeStr = selectedEncryptor.substring(selectedEncryptor.lastIndexOf('(') + 1, selectedEncryptor.lastIndexOf(')'));
|
||||
encryptorType = (encryptorTypeStr === BackupConstants.serverCertificate ? 0 : 1);
|
||||
encryptorName = selectedEncryptor.substring(0, selectedEncryptor.lastIndexOf('('));
|
||||
}
|
||||
|
||||
let backupInfo = <MssqlBackupInfo>{
|
||||
ownerUri: this._uri,
|
||||
databaseName: this.databaseName,
|
||||
backupType: this.getBackupTypeNumber(),
|
||||
backupComponent: 0,
|
||||
backupDeviceType: BackupConstants.backupDeviceTypeDisk,
|
||||
backupPathList: backupPathArray,
|
||||
selectedFiles: this.selectedFilesText,
|
||||
backupsetName: this.backupNameBox.value,
|
||||
selectedFileGroup: undefined,
|
||||
backupPathDevices: this.backupPathTypePairs,
|
||||
isCopyOnly: this.copyOnlyCheckBox.checked,
|
||||
|
||||
// Get advanced options
|
||||
formatMedia: this.isFormatChecked,
|
||||
initialize: (this.isFormatChecked ? true : (this.selectedInitOption === this.existingMediaOptions[1])),
|
||||
skipTapeHeader: this.isFormatChecked,
|
||||
mediaName: (this.isFormatChecked ? this.mediaNameBox.value : ''),
|
||||
mediaDescription: (this.isFormatChecked ? this.mediaDescriptionBox.value : ''),
|
||||
checksum: this.checksumCheckBox.checked,
|
||||
continueAfterError: this.continueOnErrorCheckBox.checked,
|
||||
logTruncation: this.isTruncateChecked,
|
||||
tailLogBackup: this.isTaillogChecked,
|
||||
retainDays: DialogHelper.isNullOrWhiteSpace(this.backupRetainDaysBox.value) ? 0 : this.backupRetainDaysBox.value,
|
||||
compressionOption: this.compressionOptions.indexOf(this.compressionSelectBox.value),
|
||||
verifyBackupRequired: this.verifyCheckBox.checked,
|
||||
encryptionAlgorithm: (this.encryptCheckBox.checked ? this.encryptionAlgorithms.indexOf(this.algorithmSelectBox.value) : 0),
|
||||
encryptorType: encryptorType,
|
||||
encryptorName: encryptorName
|
||||
};
|
||||
|
||||
return backupInfo;
|
||||
}
|
||||
}
|
||||
45
src/sql/parts/disasterRecovery/backup/backup.module.ts
Normal file
45
src/sql/parts/disasterRecovery/backup/backup.module.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ApplicationRef, ComponentFactoryResolver, ModuleWithProviders, NgModule,
|
||||
Inject, forwardRef } from '@angular/core';
|
||||
import { APP_BASE_HREF, CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { BackupComponent, BACKUP_SELECTOR } from 'sql/parts/disasterRecovery/backup/backup.component';
|
||||
|
||||
// work around
|
||||
const BrowserAnimationsModule = (<any> require.__$__nodeRequire('@angular/platform-browser/animations')).BrowserAnimationsModule;
|
||||
|
||||
// Backup wizard main angular module
|
||||
@NgModule({
|
||||
declarations: [
|
||||
BackupComponent
|
||||
],
|
||||
entryComponents: [BackupComponent],
|
||||
imports: [
|
||||
FormsModule,
|
||||
CommonModule,
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
],
|
||||
providers: [{ provide: APP_BASE_HREF, useValue: '/' }]
|
||||
})
|
||||
export class BackupModule {
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
|
||||
@Inject(BOOTSTRAP_SERVICE_ID) private _bootstrapService: IBootstrapService
|
||||
) {
|
||||
}
|
||||
|
||||
ngDoBootstrap(appRef: ApplicationRef) {
|
||||
const factory = this._resolver.resolveComponentFactory(BackupComponent);
|
||||
const uniqueSelector: string = this._bootstrapService.getUniqueSelector(BACKUP_SELECTOR);
|
||||
(<any>factory).factory.selector = uniqueSelector;
|
||||
appRef.bootstrap(factory);
|
||||
}
|
||||
}
|
||||
105
src/sql/parts/disasterRecovery/backup/backupDialog.ts
Normal file
105
src/sql/parts/disasterRecovery/backup/backupDialog.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Modal } from 'sql/base/browser/ui/modal/modal';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import { BackupModule } from 'sql/parts/disasterRecovery/backup/backup.module';
|
||||
import { BACKUP_SELECTOR } from 'sql/parts/disasterRecovery/backup/backup.component';
|
||||
import { IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { attachModalDialogStyler } from 'sql/common/theme/styler';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { Builder } from 'vs/base/browser/builder';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export class BackupDialog extends Modal {
|
||||
private _bodyBuilder: Builder;
|
||||
private _backupTitle: string;
|
||||
private _uniqueSelector: string;
|
||||
private _moduleRef: any;
|
||||
|
||||
constructor(
|
||||
@IBootstrapService private _bootstrapService: IBootstrapService,
|
||||
@IThemeService private _themeService: IThemeService,
|
||||
@IPartService partService: IPartService,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
) {
|
||||
super('', TelemetryKeys.Backup, partService, telemetryService, contextKeyService, { isAngular: true, hasErrors: true });
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement) {
|
||||
new Builder(container).div({ 'class': 'backup-dialog' }, (builder) => {
|
||||
this._bodyBuilder = builder;
|
||||
});
|
||||
}
|
||||
|
||||
public render() {
|
||||
super.render();
|
||||
attachModalDialogStyler(this, this._themeService);
|
||||
|
||||
// Add angular component template to dialog body
|
||||
this.bootstrapAngular(this._bodyBuilder.getHTMLElement());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bootstrap params and perform the bootstrap
|
||||
*/
|
||||
private bootstrapAngular(bodyContainer: HTMLElement) {
|
||||
this._uniqueSelector = this._bootstrapService.bootstrap(
|
||||
BackupModule,
|
||||
bodyContainer,
|
||||
BACKUP_SELECTOR,
|
||||
undefined,
|
||||
undefined,
|
||||
(moduleRef) => this._moduleRef = moduleRef);
|
||||
}
|
||||
|
||||
public hideError() {
|
||||
this.showError('');
|
||||
}
|
||||
|
||||
public showError(err: string) {
|
||||
this.showError(err);
|
||||
}
|
||||
|
||||
/* Overwrite escape key behavior */
|
||||
protected onClose() {
|
||||
this.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the module and DOM element and close the dialog
|
||||
*/
|
||||
public close() {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
if (this._moduleRef) {
|
||||
this._moduleRef.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the dialog
|
||||
*/
|
||||
public open(connection: IConnectionProfile) {
|
||||
this._backupTitle = 'Backup database - ' + connection.serverName + ':' + connection.databaseName;
|
||||
this.title = this._backupTitle;
|
||||
this.show();
|
||||
}
|
||||
|
||||
protected layout(height?: number): void {
|
||||
// Nothing currently laid out in this class
|
||||
}
|
||||
|
||||
}
|
||||
48
src/sql/parts/disasterRecovery/backup/constants.ts
Normal file
48
src/sql/parts/disasterRecovery/backup/constants.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
// Constants
|
||||
export const maxDevices: number = 64;
|
||||
|
||||
// Constants for backup physical device type
|
||||
export const backupDeviceTypeDisk = 2;
|
||||
export const backupDeviceTypeTape = 5;
|
||||
export const backupDeviceTypeURL = 9;
|
||||
|
||||
// Constants for backup media device type
|
||||
export const deviceTypeLogicalDevice = 0;
|
||||
export const deviceTypeTape = 1;
|
||||
export const deviceTypeFile = 2;
|
||||
export const deviceTypeURL = 5;
|
||||
|
||||
export const recoveryModelSimple = 'Simple';
|
||||
export const recoveryModelFull = 'Full';
|
||||
|
||||
// Constants for UI strings
|
||||
export const labelDatabase = 'Database';
|
||||
export const labelFilegroup = 'Files and filegroups';
|
||||
export const labelFull = 'Full';
|
||||
export const labelDifferential = 'Differential';
|
||||
export const labelLog = 'Transaction Log';
|
||||
export const labelDisk = 'Disk';
|
||||
export const labelUrl = 'Url';
|
||||
|
||||
export const defaultCompression = 'Use the default server setting';
|
||||
export const compressionOn = 'Compress backup';
|
||||
export const compressionOff = 'Do not compress backup';
|
||||
|
||||
export const aes128 = 'AES 128';
|
||||
export const aes192 = 'AES 192';
|
||||
export const aes256 = 'AES 256';
|
||||
export const tripleDES = 'Triple DES';
|
||||
|
||||
export const serverCertificate = "Server Certificate";
|
||||
export const asymmetricKey = "Asymmetric Key";
|
||||
|
||||
export const fileFiltersSet: [ {label: string, filters: string[]} ] = [
|
||||
{ label: localize('backup.filterBackupFiles', "Backup Files"), filters: ['*.bak', '*.trn', '*.log'] },
|
||||
{ label: localize('backup.allFiles', "All Files"), filters: ['*'] }
|
||||
];
|
||||
58
src/sql/parts/disasterRecovery/backup/media/backupDialog.css
Normal file
58
src/sql/parts/disasterRecovery/backup/media/backupDialog.css
Normal file
@@ -0,0 +1,58 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
.backup-path-list {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.backup-path-table {
|
||||
border: 0px;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0px;
|
||||
}
|
||||
|
||||
.backup-path-button {
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
.backup-dialog {
|
||||
height: calc(100% - 15px)
|
||||
}
|
||||
|
||||
.advanced-main-header {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.advanced-header {
|
||||
padding-top: 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
margin-top: -2px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.backup-dialog .indent {
|
||||
margin-left: 7px;
|
||||
}
|
||||
|
||||
.backup-dialog .radio-indent {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.backup-dialog .option {
|
||||
width: 100%;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
.backup-dialog .icon.warning {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.backup-dialog .warning-message{
|
||||
padding-left: 20px;
|
||||
}
|
||||
149
src/sql/parts/disasterRecovery/common/disasterRecoveryService.ts
Normal file
149
src/sql/parts/disasterRecovery/common/disasterRecoveryService.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import data = require('data');
|
||||
import { IDisasterRecoveryService, TaskExecutionMode } from 'sql/parts/disasterRecovery/common/interfaces';
|
||||
import * as Constants from 'sql/common/constants';
|
||||
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||
import * as TelemetryUtils from 'sql/common/telemetryUtilities';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
export class DisasterRecoveryService implements IDisasterRecoveryService {
|
||||
|
||||
public _serviceBrand: any;
|
||||
private _providers: { [handle: string]: data.DisasterRecoveryProvider; } = Object.create(null);
|
||||
|
||||
constructor(
|
||||
@IConnectionManagementService private _connectionService: IConnectionManagementService,
|
||||
@ITelemetryService private _telemetryService: ITelemetryService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database metadata needed to populate backup UI
|
||||
*/
|
||||
public getBackupConfigInfo(connectionUri: string): Thenable<data.BackupConfigInfo> {
|
||||
let providerId: string = this._connectionService.getProviderIdFromUri(connectionUri);
|
||||
if (providerId) {
|
||||
let provider = this._providers[providerId];
|
||||
if (provider) {
|
||||
return provider.getBackupConfigInfo(connectionUri);
|
||||
}
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Backup a data source using the provided connection
|
||||
*/
|
||||
public backup(connectionUri: string, backupInfo: { [key: string]: any }, taskExecutionMode: TaskExecutionMode): Thenable<data.BackupResponse> {
|
||||
return new Promise<data.BackupResponse>((resolve, reject) => {
|
||||
let providerResult = this.getProvider(connectionUri);
|
||||
if (providerResult) {
|
||||
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.BackupCreated, { provider: providerResult.providerName });
|
||||
providerResult.provider.backup(connectionUri, backupInfo, taskExecutionMode).then(result => {
|
||||
resolve(result);
|
||||
}, error => {
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
reject(Constants.InvalidProvider);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets restore config Info
|
||||
*/
|
||||
getRestoreConfigInfo(connectionUri: string): Thenable<data.RestoreConfigInfo> {
|
||||
return new Promise<data.RestoreConfigInfo>((resolve, reject) => {
|
||||
let providerResult = this.getProvider(connectionUri);
|
||||
if (providerResult) {
|
||||
providerResult.provider.getRestoreConfigInfo(connectionUri).then(result => {
|
||||
resolve(result);
|
||||
}, error => {
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
reject(Constants.InvalidProvider);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a data source using a backup file or database
|
||||
*/
|
||||
restore(connectionUri: string, restoreInfo: data.RestoreInfo): Thenable<data.RestoreResponse> {
|
||||
return new Promise<data.RestoreResponse>((resolve, reject) => {
|
||||
let providerResult = this.getProvider(connectionUri);
|
||||
if (providerResult) {
|
||||
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.RestoreRequested, { provider: providerResult.providerName });
|
||||
providerResult.provider.restore(connectionUri, restoreInfo).then(result => {
|
||||
resolve(result);
|
||||
}, error => {
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
reject(Constants.InvalidProvider);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getProvider(connectionUri: string): { provider: data.DisasterRecoveryProvider, providerName: string } {
|
||||
let providerId: string = this._connectionService.getProviderIdFromUri(connectionUri);
|
||||
if (providerId) {
|
||||
return { provider: this._providers[providerId], providerName: providerId };
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets restore plan to do the restore operation on a database
|
||||
*/
|
||||
getRestorePlan(connectionUri: string, restoreInfo: data.RestoreInfo): Thenable<data.RestorePlanResponse> {
|
||||
return new Promise<data.RestorePlanResponse>((resolve, reject) => {
|
||||
let providerResult = this.getProvider(connectionUri);
|
||||
if (providerResult) {
|
||||
providerResult.provider.getRestorePlan(connectionUri, restoreInfo).then(result => {
|
||||
resolve(result);
|
||||
}, error => {
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
reject(Constants.InvalidProvider);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a restore plan
|
||||
*/
|
||||
cancelRestorePlan(connectionUri: string, restoreInfo: data.RestoreInfo): Thenable<boolean> {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
let providerResult = this.getProvider(connectionUri);
|
||||
if (providerResult) {
|
||||
providerResult.provider.cancelRestorePlan(connectionUri, restoreInfo).then(result => {
|
||||
resolve(result);
|
||||
}, error => {
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
reject(Constants.InvalidProvider);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a disaster recovery provider
|
||||
*/
|
||||
public registerProvider(providerId: string, provider: data.DisasterRecoveryProvider): void {
|
||||
this._providers[providerId] = provider;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import * as data from 'data';
|
||||
|
||||
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import { OptionsDialog } from 'sql/base/browser/ui/modal/optionsDialog';
|
||||
import { BackupDialog } from 'sql/parts/disasterRecovery/backup/backupDialog';
|
||||
import { IDisasterRecoveryService, IDisasterRecoveryUiService, TaskExecutionMode } from 'sql/parts/disasterRecovery/common/interfaces';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import ConnectionUtils = require('sql/parts/connection/common/utils');
|
||||
import { ProviderConnectionInfo } from 'sql/parts/connection/common/providerConnectionInfo';
|
||||
import { DashboardComponentParams } from 'sql/services/bootstrap/bootstrapParams';
|
||||
import * as Utils from 'sql/parts/connection/common/utils';
|
||||
|
||||
export class DisasterRecoveryUiService implements IDisasterRecoveryUiService {
|
||||
public _serviceBrand: any;
|
||||
private _backupDialogs: { [providerName: string]: BackupDialog | OptionsDialog } = {};
|
||||
private _currentProvider: string;
|
||||
private _optionsMap: { [providerName: string]: data.ServiceOption[] } = {};
|
||||
private _optionValues: { [optionName: string]: any } = {};
|
||||
private _connectionUri: string;
|
||||
private static _connectionUniqueId: number = 0;
|
||||
|
||||
private _onShowBackupEvent: Emitter<DashboardComponentParams>;
|
||||
public get onShowBackupEvent(): Event<DashboardComponentParams> { return this._onShowBackupEvent.event; }
|
||||
|
||||
constructor( @IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IPartService private _partService: IPartService,
|
||||
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
|
||||
@IDisasterRecoveryService private _disasterRecoveryService: IDisasterRecoveryService,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService) {
|
||||
this._onShowBackupEvent = new Emitter<DashboardComponentParams>();
|
||||
}
|
||||
|
||||
public showBackup(connection: IConnectionProfile): Promise<any> {
|
||||
let self = this;
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
self.showBackupDialog(connection).then(() => {
|
||||
resolve();
|
||||
}, error => {
|
||||
reject();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public showBackupDialog(connection: IConnectionProfile): TPromise<void> {
|
||||
let self = this;
|
||||
self._connectionUri = ConnectionUtils.generateUri(connection);
|
||||
self._currentProvider = connection.providerName;
|
||||
let backupDialog = self._backupDialogs[self._currentProvider];
|
||||
if (!backupDialog) {
|
||||
let capabilitiesList = this._capabilitiesService.getCapabilities();
|
||||
capabilitiesList.forEach(providerCapabilities => {
|
||||
let backupFeature = providerCapabilities.features.find(feature => feature.featureName === 'backup');
|
||||
if (backupFeature && backupFeature.optionsMetadata) {
|
||||
this._optionsMap[providerCapabilities.providerName] = backupFeature.optionsMetadata;
|
||||
}
|
||||
});
|
||||
let backupOptions = self._optionsMap[self._currentProvider];
|
||||
if (backupOptions) {
|
||||
backupDialog = self._instantiationService ? self._instantiationService.createInstance(
|
||||
OptionsDialog, 'Backup database - ' + connection.serverName + ':' + connection.databaseName, 'BackupOptions', undefined) : undefined;
|
||||
backupDialog.onOk(() => this.handleOptionDialogClosed());
|
||||
}
|
||||
else {
|
||||
backupDialog = self._instantiationService ? self._instantiationService.createInstance(BackupDialog) : undefined;
|
||||
}
|
||||
backupDialog.render();
|
||||
self._backupDialogs[self._currentProvider] = backupDialog;
|
||||
}
|
||||
|
||||
let backupOptions = this._optionsMap[self._currentProvider];
|
||||
return new TPromise<void>(() => {
|
||||
if (backupOptions) {
|
||||
(backupDialog as OptionsDialog).open(backupOptions, self._optionValues);
|
||||
} else {
|
||||
let uri = this._connectionManagementService.getConnectionId(connection)
|
||||
+ ProviderConnectionInfo.idSeparator
|
||||
+ Utils.ConnectionUriBackupIdAttributeName
|
||||
+ ProviderConnectionInfo.nameValueSeparator
|
||||
+ DisasterRecoveryUiService._connectionUniqueId;
|
||||
|
||||
DisasterRecoveryUiService._connectionUniqueId++;
|
||||
|
||||
// Create connection if needed
|
||||
if (!this._connectionManagementService.isConnected(uri)) {
|
||||
this._connectionManagementService.connect(connection, uri).then(() => {
|
||||
this._onShowBackupEvent.fire({connection: connection, ownerUri: uri});
|
||||
});
|
||||
}
|
||||
(backupDialog as BackupDialog).open(connection);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public closeBackup() {
|
||||
let self = this;
|
||||
let backupDialog = self._backupDialogs[self._currentProvider];
|
||||
if (backupDialog) {
|
||||
backupDialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
private handleOptionDialogClosed() {
|
||||
this._disasterRecoveryService.backup(this._connectionUri, this._optionValues, TaskExecutionMode.executeAndScript);
|
||||
}
|
||||
|
||||
}
|
||||
89
src/sql/parts/disasterRecovery/common/interfaces.ts
Normal file
89
src/sql/parts/disasterRecovery/common/interfaces.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import Event from 'vs/base/common/event';
|
||||
import data = require('data');
|
||||
|
||||
import { DashboardComponentParams } from 'sql/services/bootstrap/bootstrapParams';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
|
||||
|
||||
export enum TaskExecutionMode {
|
||||
execute = 0,
|
||||
script = 1,
|
||||
executeAndScript = 2,
|
||||
}
|
||||
|
||||
export const SERVICE_ID = 'disasterRecoveryService';
|
||||
export const UI_SERVICE_ID = 'disasterRecoveryUiService';
|
||||
|
||||
export const IDisasterRecoveryUiService = createDecorator<IDisasterRecoveryUiService>(UI_SERVICE_ID);
|
||||
|
||||
export interface IDisasterRecoveryUiService {
|
||||
_serviceBrand: any;
|
||||
|
||||
/**
|
||||
* Show backup wizard
|
||||
*/
|
||||
showBackup(connection: IConnectionProfile): Promise<any>;
|
||||
|
||||
/**
|
||||
* On show backup event
|
||||
*/
|
||||
onShowBackupEvent: Event<DashboardComponentParams>;
|
||||
|
||||
/**
|
||||
* Close backup wizard
|
||||
*/
|
||||
closeBackup();
|
||||
}
|
||||
|
||||
export const IDisasterRecoveryService = createDecorator<IDisasterRecoveryService>(SERVICE_ID);
|
||||
|
||||
export interface IDisasterRecoveryService {
|
||||
_serviceBrand: any;
|
||||
|
||||
getBackupConfigInfo(connectionUri: string): Thenable<data.BackupConfigInfo>;
|
||||
|
||||
/**
|
||||
* Backup a data source using the provided connection
|
||||
*/
|
||||
backup(connectionUri: string, backupInfo: { [key: string]: any }, taskExecutionMode: data.TaskExecutionMode): Thenable<data.BackupResponse>;
|
||||
|
||||
/**
|
||||
* Register a disaster recovery provider
|
||||
*/
|
||||
registerProvider(providerId: string, provider: data.DisasterRecoveryProvider): void;
|
||||
|
||||
/**
|
||||
* Restore a data source using a backup file or database
|
||||
*/
|
||||
restore(connectionUri: string, restoreInfo: data.RestoreInfo): Thenable<data.RestoreResponse>;
|
||||
|
||||
/**
|
||||
* Gets restore plan to do the restore operation on a database
|
||||
*/
|
||||
getRestorePlan(connectionUri: string, restoreInfo: data.RestoreInfo): Thenable<data.RestorePlanResponse>;
|
||||
|
||||
/**
|
||||
* Gets restore config Info
|
||||
*/
|
||||
getRestoreConfigInfo(connectionUri: string): Thenable<data.RestoreConfigInfo>;
|
||||
|
||||
/**
|
||||
* Cancel restore plan
|
||||
*/
|
||||
cancelRestorePlan(connectionUri: string, restoreInfo: data.RestoreInfo): Thenable<boolean>;
|
||||
}
|
||||
|
||||
export const IRestoreDialogController = createDecorator<IRestoreDialogController>('restoreDialogService');
|
||||
export interface IRestoreDialogController {
|
||||
_serviceBrand: any;
|
||||
showDialog(connection: IConnectionProfile): TPromise<void>;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.restore-dialog {
|
||||
padding: 15px
|
||||
}
|
||||
|
||||
.modal .restore-dialog .dialog-label.header {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.modal .restore-dialog .new-section {
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
.modal .restore-dialog .sub-section {
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.modal .restore-dialog .dialog-input-section {
|
||||
display: flex;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.modal .restore-dialog .dialog-input-section .dialog-label {
|
||||
flex: 0 0 100px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.modal .restore-dialog .dialog-input-section .dialog-input {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.modal .restore-dialog .file-browser {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.modal .restore-dialog .file-browser .monaco-button {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.modal .restore-dialog .restore-plan-section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal .restore-dialog .restore-list {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
margin-bottom: 15px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.modal .restore-dialog .restore-list .backup-table-content .restore-data {
|
||||
white-space: nowrap;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.modal .restore-dialog .more-options-button {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.modal .restore-dialog .more-options-button a.monaco-button.monaco-text-button {
|
||||
margin-right: 15px;
|
||||
}
|
||||
98
src/sql/parts/disasterRecovery/restore/mssqlRestoreInfo.ts
Normal file
98
src/sql/parts/disasterRecovery/restore/mssqlRestoreInfo.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import data = require('data');
|
||||
|
||||
export class MssqlRestoreInfo implements data.RestoreInfo {
|
||||
|
||||
options: { [name: string]: any };
|
||||
taskExecutionMode: data.TaskExecutionMode;
|
||||
|
||||
public constructor() {
|
||||
this.options = {};
|
||||
}
|
||||
|
||||
public get sessionId(): string {
|
||||
return this.options['sessionId'];
|
||||
}
|
||||
|
||||
public set sessionId(value: string) {
|
||||
this.options['sessionId'] = value;
|
||||
}
|
||||
|
||||
public get backupFilePaths(): string {
|
||||
return this.options['backupFilePaths'];
|
||||
}
|
||||
|
||||
public set backupFilePaths(value: string) {
|
||||
this.options['backupFilePaths'] = value;
|
||||
}
|
||||
|
||||
public get targetDatabaseName(): string {
|
||||
return this.options['targetDatabaseName'];
|
||||
}
|
||||
|
||||
public set targetDatabaseName(value: string) {
|
||||
this.options['targetDatabaseName'] = value;
|
||||
}
|
||||
|
||||
public get sourceDatabaseName(): string {
|
||||
return this.options['sourceDatabaseName'];
|
||||
}
|
||||
|
||||
public set sourceDatabaseName(value: string) {
|
||||
this.options['sourceDatabaseName'] = value;
|
||||
}
|
||||
|
||||
public get relocateDbFiles(): boolean {
|
||||
return this.options['relocateDbFiles'];
|
||||
}
|
||||
|
||||
public set relocateDbFiles(value: boolean) {
|
||||
this.options['relocateDbFiles'] = value;
|
||||
}
|
||||
|
||||
public get dataFileFolder(): string {
|
||||
return this.options['dataFileFolder'];
|
||||
}
|
||||
|
||||
public set dataFileFolder(value: string) {
|
||||
this.options['dataFileFolder'] = value;
|
||||
}
|
||||
|
||||
public get logFileFolder(): string {
|
||||
return this.options['logFileFolder'];
|
||||
}
|
||||
|
||||
public set logFileFolder(value: string) {
|
||||
this.options['logFileFolder'] = value;
|
||||
}
|
||||
|
||||
public get selectedBackupSets(): string[] {
|
||||
return this.options['selectedBackupSets'];
|
||||
}
|
||||
|
||||
public set selectedBackupSets(value: string[]) {
|
||||
this.options['selectedBackupSets'] = value;
|
||||
}
|
||||
|
||||
public get readHeaderFromMedia(): boolean {
|
||||
return this.options['readHeaderFromMedia'];
|
||||
}
|
||||
|
||||
public set readHeaderFromMedia(value: boolean) {
|
||||
this.options['readHeaderFromMedia'] = value;
|
||||
}
|
||||
|
||||
public get overwriteTargetDatabase(): boolean {
|
||||
return this.options['overwriteTargetDatabase'];
|
||||
}
|
||||
|
||||
public set overwriteTargetDatabase(value: boolean) {
|
||||
this.options['overwriteTargetDatabase'] = value;
|
||||
}
|
||||
}
|
||||
815
src/sql/parts/disasterRecovery/restore/restoreDialog.ts
Normal file
815
src/sql/parts/disasterRecovery/restore/restoreDialog.ts
Normal file
@@ -0,0 +1,815 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import 'vs/css!./media/restoreDialog';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { MessageType, IInputOptions } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { attachButtonStyler, attachCheckboxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox';
|
||||
import { InputBox, OnLoseFocusParams } from 'sql/base/browser/ui/inputBox/inputBox';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { RowSelectionModel } from 'sql/base/browser/ui/table/plugins/rowSelectionModel.plugin';
|
||||
import { CheckboxSelectColumn } from 'sql/base/browser/ui/table/plugins/checkboxSelectColumn.plugin';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
|
||||
import * as DialogHelper from 'sql/base/browser/ui/modal/dialogHelper';
|
||||
import { Modal } from 'sql/base/browser/ui/modal/modal';
|
||||
import { attachModalDialogStyler, attachTableStyler, attachInputBoxStyler, attachSelectBoxStyler, attachEditableDropdownStyler } from 'sql/common/theme/styler';
|
||||
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||
import { ServiceOptionType } from 'sql/parts/connection/common/connectionManagement';
|
||||
import * as BackupConstants from 'sql/parts/disasterRecovery/backup/constants';
|
||||
import { RestoreViewModel, RestoreOptionParam, SouceDatabaseNamesParam } from 'sql/parts/disasterRecovery/restore/restoreViewModel';
|
||||
import * as FileValidationConstants from 'sql/parts/fileBrowser/common/fileValidationServiceConstants';
|
||||
import { IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { Dropdown } from 'sql/base/browser/ui/editableDropdown/dropdown';
|
||||
import { TabbedPanel, PanelTabIdentifier } from 'sql/base/browser/ui/panel/panel';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as data from 'data';
|
||||
|
||||
interface FileListElement {
|
||||
logicalFileName: string;
|
||||
fileType: string;
|
||||
originalFileName: string;
|
||||
restoreAs: string;
|
||||
}
|
||||
|
||||
export class RestoreDialog extends Modal {
|
||||
public viewModel: RestoreViewModel;
|
||||
|
||||
private _scriptButton: Button;
|
||||
private _restoreButton: Button;
|
||||
private _closeButton: Button;
|
||||
private _optionsMap: { [name: string]: Widget } = {};
|
||||
private _restoreLabel: string;
|
||||
private _restoreTitle: string;
|
||||
private _databaseTitle: string;
|
||||
private _backupFileTitle: string;
|
||||
private _ownerUri: string;
|
||||
private _databaseDropdown: Dropdown;
|
||||
|
||||
// General options
|
||||
private _filePathInputBox: InputBox;
|
||||
private _browseFileButton: Button;
|
||||
private _destinationRestoreToInputBox: InputBox;
|
||||
private _restoreFromSelectBox: SelectBox;
|
||||
private _sourceDatabaseSelectBox: SelectBox;
|
||||
|
||||
private _panel: TabbedPanel;
|
||||
private _generalTabId: PanelTabIdentifier;
|
||||
|
||||
// File option
|
||||
private readonly _relocateDatabaseFilesOption = 'relocateDbFiles';
|
||||
private readonly _relocatedDataFileFolderOption = 'dataFileFolder';
|
||||
private readonly _relocatedLogFileFolderOption = 'logFileFolder';
|
||||
|
||||
// other options
|
||||
private readonly _withReplaceDatabaseOption = 'replaceDatabase';
|
||||
private readonly _withKeepReplicationOption = 'keepReplication';
|
||||
private readonly _withRestrictedUserOption = 'setRestrictedUser';
|
||||
|
||||
private readonly _recoveryStateOption = 'recoveryState';
|
||||
private readonly _standbyFileOption = 'standbyFile';
|
||||
|
||||
private readonly _takeTaillogBackupOption = 'backupTailLog';
|
||||
private readonly _tailLogWithNoRecoveryOption = 'tailLogWithNoRecovery';
|
||||
private readonly _tailLogBackupFileOption = 'tailLogBackupFile';
|
||||
|
||||
private readonly _closeExistingConnectionsOption = 'closeExistingConnections';
|
||||
|
||||
private _restoreFromBackupFileElement: HTMLElement;
|
||||
|
||||
private _fileListTable: Table<FileListElement>;
|
||||
private _fileListData: TableDataView<FileListElement>;
|
||||
|
||||
private _restorePlanTable: Table<Slick.SlickData>;
|
||||
private _restorePlanData: TableDataView<Slick.SlickData>;
|
||||
private _restorePlanColumn;
|
||||
|
||||
private _onRestore = new Emitter<boolean>();
|
||||
public onRestore: Event<boolean> = this._onRestore.event;
|
||||
|
||||
private _onValidate = new Emitter<boolean>();
|
||||
public onValidate: Event<boolean> = this._onValidate.event;
|
||||
|
||||
private _onCancel = new Emitter<void>();
|
||||
public onCancel: Event<void> = this._onCancel.event;
|
||||
|
||||
private _onCloseEvent = new Emitter<void>();
|
||||
public onCloseEvent: Event<void> = this._onCloseEvent.event;
|
||||
|
||||
private _onDatabaseListFocused = new Emitter<void>();
|
||||
public onDatabaseListFocused: Event<void> = this._onDatabaseListFocused.event;
|
||||
|
||||
constructor(
|
||||
optionsMetadata: data.ServiceOption[],
|
||||
@IPartService partService: IPartService,
|
||||
@IThemeService private _themeService: IThemeService,
|
||||
@IContextViewService private _contextViewService: IContextViewService,
|
||||
@IBootstrapService private _bootstrapService: IBootstrapService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
) {
|
||||
super(localize('RestoreDialogTitle', 'Restore database'), TelemetryKeys.Restore, partService, telemetryService, contextKeyService, { hasErrors: true, isWide: true, hasSpinner: true });
|
||||
this._restoreTitle = localize('restoreTitle', 'Restore database');
|
||||
this._databaseTitle = localize('database', 'Database');
|
||||
this._backupFileTitle = localize('backupFile', 'Backup file');
|
||||
this._restoreLabel = localize('restore', 'Restore');
|
||||
|
||||
// view model
|
||||
this.viewModel = new RestoreViewModel(optionsMetadata);
|
||||
this.viewModel.onSetLastBackupTaken((value) => this.updateLastBackupTaken(value));
|
||||
this.viewModel.onSetfilePath((value) => this.updateFilePath(value));
|
||||
this.viewModel.onSetSourceDatabaseNames((databaseNamesParam) => this.updateSourceDatabaseName(databaseNamesParam));
|
||||
this.viewModel.onSetTargetDatabaseName((value) => this.updateTargetDatabaseName(value));
|
||||
this.viewModel.onSetLastBackupTaken((value) => this.updateLastBackupTaken(value));
|
||||
this.viewModel.onSetRestoreOption((optionParams) => this.updateRestoreOption(optionParams));
|
||||
this.viewModel.onUpdateBackupSetsToRestore((backupSets) => this.updateBackupSetsToRestore(backupSets));
|
||||
this.viewModel.onUpdateRestoreDatabaseFiles((files) => this.updateRestoreDatabaseFiles(files));
|
||||
}
|
||||
|
||||
public render() {
|
||||
super.render();
|
||||
attachModalDialogStyler(this, this._themeService);
|
||||
let cancelLabel = localize('cancel', 'Cancel');
|
||||
this._scriptButton = this.addFooterButton(localize('script', 'Script'), () => this.restore(true));
|
||||
this._restoreButton = this.addFooterButton(this._restoreLabel, () => this.restore(false));
|
||||
this._closeButton = this.addFooterButton(cancelLabel, () => this.cancel());
|
||||
this.registerListeners();
|
||||
this._destinationRestoreToInputBox.disable();
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement) {
|
||||
|
||||
let restoreFromElement;
|
||||
$().div({ class: 'restore-from' }, (restoreFromContainer) => {
|
||||
restoreFromElement = restoreFromContainer.getHTMLElement();
|
||||
this.createLabelElement(restoreFromContainer, localize('source', 'Source'), true);
|
||||
this._restoreFromSelectBox = this.createSelectBoxHelper(restoreFromContainer, localize('restoreFrom', 'Restore from'), [this._databaseTitle, this._backupFileTitle], this._databaseTitle);
|
||||
});
|
||||
|
||||
$().div({ class: 'backup-file-path' }, (filePathContainer) => {
|
||||
filePathContainer.hide();
|
||||
this._restoreFromBackupFileElement = filePathContainer.getHTMLElement();
|
||||
let errorMessage = localize('missingBackupFilePathError', 'Backup file path is required.');
|
||||
let validationOptions: IInputOptions = {
|
||||
validationOptions: {
|
||||
validation: (value: string) => !value ? ({ type: MessageType.ERROR, content: errorMessage }) : null
|
||||
},
|
||||
placeholder: localize('multipleBackupFilePath', 'Please enter one or more file paths separated by commas')
|
||||
};
|
||||
|
||||
filePathContainer.div({ class: 'dialog-input-section' }, (inputContainer) => {
|
||||
inputContainer.div({ class: 'dialog-label' }, (labelContainer) => {
|
||||
labelContainer.innerHtml(localize('backupFilePath', "Backup file path"));
|
||||
});
|
||||
|
||||
inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => {
|
||||
this._filePathInputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, validationOptions);
|
||||
});
|
||||
|
||||
inputContainer.div({ class: 'file-browser' }, (inputCellContainer) => {
|
||||
this._browseFileButton = new Button(inputCellContainer);
|
||||
this._browseFileButton.label = '...';
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let sourceDatabasesElement;
|
||||
$().div({ class: 'source-database-list' }, (sourceDatabasesContainer) => {
|
||||
sourceDatabasesElement = sourceDatabasesContainer.getHTMLElement();
|
||||
this._sourceDatabaseSelectBox = this.createSelectBoxHelper(sourceDatabasesContainer, localize('database', 'Database'), [], '');
|
||||
});
|
||||
|
||||
// Source section
|
||||
let sourceElement: HTMLElement;
|
||||
$().div({ class: 'source-section new-section' }, (sourceContainer) => {
|
||||
sourceElement = sourceContainer.getHTMLElement();
|
||||
sourceContainer.append(restoreFromElement);
|
||||
sourceContainer.append(this._restoreFromBackupFileElement);
|
||||
sourceContainer.append(sourceDatabasesElement);
|
||||
});
|
||||
|
||||
// Destination section
|
||||
let destinationElement: HTMLElement;
|
||||
$().div({ class: 'destination-section new-section' }, (destinationContainer) => {
|
||||
destinationElement = destinationContainer.getHTMLElement();
|
||||
this.createLabelElement(destinationContainer, localize('destination', 'Destination'), true);
|
||||
|
||||
destinationContainer.div({ class: 'dialog-input-section' }, (inputContainer) => {
|
||||
inputContainer.div({ class: 'dialog-label' }, (labelContainer) => {
|
||||
labelContainer.innerHtml(localize('targetDatabase', 'Target database'));
|
||||
});
|
||||
|
||||
inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => {
|
||||
// Get the bootstrap params and perform the bootstrap
|
||||
inputCellContainer.style('width', '100%');
|
||||
this._databaseDropdown = new Dropdown(inputCellContainer.getHTMLElement(), this._contextViewService, this._themeService,
|
||||
{
|
||||
strictSelection: false
|
||||
}
|
||||
);
|
||||
this._databaseDropdown.onValueChange(s => {
|
||||
this.databaseSelected(s);
|
||||
});
|
||||
|
||||
this._databaseDropdown.onBlur(() => {
|
||||
this.databaseSelected(this._databaseDropdown.value);
|
||||
});
|
||||
|
||||
this._databaseDropdown.onFocus(() => {
|
||||
this._onDatabaseListFocused.fire();
|
||||
});
|
||||
|
||||
this._databaseDropdown.value = this.viewModel.targetDatabaseName;
|
||||
attachEditableDropdownStyler(this._databaseDropdown, this._themeService);
|
||||
});
|
||||
});
|
||||
|
||||
this._destinationRestoreToInputBox = this.createInputBoxHelper(destinationContainer, localize('restoreTo', 'Restore to'));
|
||||
});
|
||||
|
||||
|
||||
// Restore plan section
|
||||
let restorePlanElement: HTMLElement;
|
||||
$().div({ class: 'restore-plan-section new-section' }, (restorePlanContainer) => {
|
||||
restorePlanElement = restorePlanContainer.getHTMLElement();
|
||||
this.createLabelElement(restorePlanContainer, localize('restorePlan', 'Restore plan'), true);
|
||||
this.createLabelElement(restorePlanContainer, localize('backupSetsToRestore', 'Backup sets to restore'));
|
||||
|
||||
// Backup sets table
|
||||
restorePlanContainer.div({ class: 'dialog-input-section restore-list' }, (labelContainer) => {
|
||||
this._restorePlanData = new TableDataView<Slick.SlickData>();
|
||||
this._restorePlanTable = new Table<Slick.SlickData>(labelContainer.getHTMLElement(), this._restorePlanData, this._restorePlanColumn, { enableColumnReorder: false });
|
||||
this._restorePlanTable.setSelectionModel(new RowSelectionModel({ selectActiveRow: false }));
|
||||
this._restorePlanTable.onSelectedRowsChanged((e, data) => this.backupFileCheckboxChanged(e, data));
|
||||
});
|
||||
});
|
||||
|
||||
// Content in general tab
|
||||
let generalTab = $('.restore-dialog');
|
||||
generalTab.append(sourceElement);
|
||||
generalTab.append(destinationElement);
|
||||
generalTab.append(restorePlanElement);
|
||||
|
||||
// Content in file tab
|
||||
let fileContentElement: HTMLElement;
|
||||
$().div({ class: 'restore-dialog' }, (builder) => {
|
||||
fileContentElement = builder.getHTMLElement();
|
||||
|
||||
// Restore database file as section
|
||||
builder.div({ class: 'new-section' }, (sectionContainer) => {
|
||||
this.createLabelElement(sectionContainer, localize('restoreDatabaseFileAs', 'Restore database files as'), true);
|
||||
this.createOptionControl(sectionContainer, this._relocateDatabaseFilesOption);
|
||||
sectionContainer.div({ class: 'sub-section' }, (subSectionContainer) => {
|
||||
this.createOptionControl(subSectionContainer, this._relocatedDataFileFolderOption);
|
||||
this.createOptionControl(subSectionContainer, this._relocatedLogFileFolderOption);
|
||||
});
|
||||
});
|
||||
|
||||
// Restore database file details section
|
||||
builder.div({ class: 'new-section' }, (sectionContainer) => {
|
||||
this.createLabelElement(sectionContainer, localize('restoreDatabaseFileDetails', 'Restore database file details'), true);
|
||||
// file list table
|
||||
sectionContainer.div({ class: 'dialog-input-section restore-list' }, (fileNameContainer) => {
|
||||
let logicalFileName = localize('logicalFileName', 'Logical file Name');
|
||||
let fileType = localize('fileType', 'File type');
|
||||
let originalFileName = localize('originalFileName', 'Original File Name');
|
||||
let restoreAs = localize('restoreAs', 'Restore as');
|
||||
var columns = [{
|
||||
id: 'logicalFileName',
|
||||
name: logicalFileName,
|
||||
field: 'logicalFileName'
|
||||
}, {
|
||||
id: 'fileType',
|
||||
name: fileType,
|
||||
field: 'fileType'
|
||||
}, {
|
||||
id: 'originalFileName',
|
||||
name: originalFileName,
|
||||
field: 'originalFileName'
|
||||
}, {
|
||||
id: 'restoreAs',
|
||||
name: restoreAs,
|
||||
field: 'restoreAs'
|
||||
}];
|
||||
this._fileListData = new TableDataView<FileListElement>();
|
||||
this._fileListTable = new Table<FileListElement>(fileNameContainer.getHTMLElement(), this._fileListData, columns, { enableColumnReorder: false });
|
||||
this._fileListTable.setSelectionModel(new RowSelectionModel());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Content in options tab
|
||||
let optionsContentElement: HTMLElement;
|
||||
$().div({ class: 'restore-dialog' }, (builder) => {
|
||||
optionsContentElement = builder.getHTMLElement();
|
||||
|
||||
// Restore options section
|
||||
builder.div({ class: 'new-section' }, (sectionContainer) => {
|
||||
this.createLabelElement(sectionContainer, localize('restoreOptions', 'Restore options'), true);
|
||||
this.createOptionControl(sectionContainer, this._withReplaceDatabaseOption);
|
||||
this.createOptionControl(sectionContainer, this._withKeepReplicationOption);
|
||||
this.createOptionControl(sectionContainer, this._withRestrictedUserOption);
|
||||
this.createOptionControl(sectionContainer, this._recoveryStateOption);
|
||||
|
||||
sectionContainer.div({ class: 'sub-section' }, (subSectionContainer) => {
|
||||
this.createOptionControl(subSectionContainer, this._standbyFileOption);
|
||||
});
|
||||
});
|
||||
|
||||
// Tail-Log backup section
|
||||
builder.div({ class: 'new-section' }, (sectionContainer) => {
|
||||
this.createLabelElement(sectionContainer, localize('taillogBackup', 'Tail-Log backup'), true);
|
||||
this.createOptionControl(sectionContainer, this._takeTaillogBackupOption);
|
||||
sectionContainer.div({ class: 'sub-section' }, (subSectionContainer) => {
|
||||
this.createOptionControl(subSectionContainer, this._tailLogWithNoRecoveryOption);
|
||||
this.createOptionControl(subSectionContainer, this._tailLogBackupFileOption);
|
||||
});
|
||||
});
|
||||
|
||||
// Server connections section
|
||||
builder.div({ class: 'new-section' }, (sectionContainer) => {
|
||||
this.createLabelElement(sectionContainer, localize('serverConnection', 'Server connections'), true);
|
||||
this.createOptionControl(sectionContainer, this._closeExistingConnectionsOption);
|
||||
});
|
||||
});
|
||||
|
||||
this._panel = new TabbedPanel(container);
|
||||
this._generalTabId = this._panel.pushTab({
|
||||
identifier: 'general',
|
||||
title: localize('generalTitle', 'General'),
|
||||
view: {
|
||||
render: c => {
|
||||
generalTab.appendTo(c);
|
||||
},
|
||||
layout: () => { }
|
||||
}
|
||||
});
|
||||
|
||||
let fileTab = this._panel.pushTab({
|
||||
identifier: 'fileContent',
|
||||
title: localize('filesTitle', 'Files'),
|
||||
view: {
|
||||
layout: () => { },
|
||||
render: c => {
|
||||
c.appendChild(fileContentElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._panel.pushTab({
|
||||
identifier: 'options',
|
||||
title: localize('optionsTitle', 'Options'),
|
||||
view: {
|
||||
layout: () => { },
|
||||
render: c => {
|
||||
c.appendChild(optionsContentElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._panel.onTabChange(c => {
|
||||
if (c === fileTab && this._fileListTable) {
|
||||
this._fileListTable.resizeCanvas();
|
||||
this._fileListTable.autosizeColumns();
|
||||
}
|
||||
if (c !== this._generalTabId) {
|
||||
this._restoreFromSelectBox.hideMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private databaseSelected(dbName: string): void {
|
||||
if (this.viewModel.targetDatabaseName !== dbName) {
|
||||
this.viewModel.targetDatabaseName = dbName;
|
||||
this.validateRestore(false);
|
||||
}
|
||||
}
|
||||
|
||||
public set databaseListOptions(vals: string[]) {
|
||||
this._databaseDropdown.values = vals;
|
||||
}
|
||||
|
||||
private createLabelElement(container: Builder, content: string, isHeader?: boolean) {
|
||||
let className = 'dialog-label';
|
||||
if (isHeader) {
|
||||
className += ' header';
|
||||
}
|
||||
container.div({ class: className }, (labelContainer) => {
|
||||
labelContainer.innerHtml(content);
|
||||
});
|
||||
}
|
||||
|
||||
private createOptionControl(container: Builder, optionName: string): void {
|
||||
let option = this.viewModel.getOptionMetadata(optionName);
|
||||
let propertyWidget: any;
|
||||
switch (option.valueType) {
|
||||
case ServiceOptionType.boolean:
|
||||
propertyWidget = this.createCheckBoxHelper(container, option.description,
|
||||
DialogHelper.getBooleanValueFromStringOrBoolean(option.defaultValue), () => this.onBooleanOptionChecked(optionName));
|
||||
this._register(attachCheckboxStyler(propertyWidget, this._themeService));
|
||||
break;
|
||||
case ServiceOptionType.category:
|
||||
propertyWidget = this.createSelectBoxHelper(container, option.description, option.categoryValues.map(c => c.displayName), DialogHelper.getCategoryDisplayName(option.categoryValues, option.defaultValue));
|
||||
this._register(attachSelectBoxStyler(propertyWidget, this._themeService));
|
||||
this._register(propertyWidget.onDidSelect(selectedDatabase => {
|
||||
this.onCatagoryOptionChanged(optionName);
|
||||
}));
|
||||
break;
|
||||
case ServiceOptionType.string:
|
||||
propertyWidget = this.createInputBoxHelper(container, option.description);
|
||||
this._register(attachInputBoxStyler(propertyWidget, this._themeService));
|
||||
this._register(propertyWidget.onLoseFocus(params => {
|
||||
this.onStringOptionChanged(optionName, params);
|
||||
}));
|
||||
}
|
||||
|
||||
this._optionsMap[optionName] = propertyWidget;
|
||||
}
|
||||
|
||||
private onBooleanOptionChecked(optionName: string) {
|
||||
this.viewModel.setOptionValue(optionName, (<Checkbox>this._optionsMap[optionName]).checked);
|
||||
this.validateRestore(false);
|
||||
}
|
||||
|
||||
private onCatagoryOptionChanged(optionName: string) {
|
||||
this.viewModel.setOptionValue(optionName, (<SelectBox>this._optionsMap[optionName]).value);
|
||||
this.validateRestore(false);
|
||||
}
|
||||
|
||||
private onStringOptionChanged(optionName: string, params: OnLoseFocusParams) {
|
||||
if (params.hasChanged && params.value) {
|
||||
this.viewModel.setOptionValue(optionName, params.value);
|
||||
this.validateRestore(false);
|
||||
}
|
||||
}
|
||||
|
||||
private createCheckBoxHelper(container: Builder, label: string, isChecked: boolean, onCheck: (viaKeyboard: boolean) => void): Checkbox {
|
||||
let checkbox: Checkbox;
|
||||
container.div({ class: 'dialog-input-section' }, (inputCellContainer) => {
|
||||
checkbox = DialogHelper.createCheckBox(inputCellContainer, label, 'restore-checkbox', isChecked, onCheck);
|
||||
});
|
||||
return checkbox;
|
||||
}
|
||||
|
||||
private createSelectBoxHelper(container: Builder, label: string, options: string[], selectedOption: string): SelectBox {
|
||||
let selectBox: SelectBox;
|
||||
container.div({ class: 'dialog-input-section' }, (inputContainer) => {
|
||||
inputContainer.div({ class: 'dialog-label' }, (labelContainer) => {
|
||||
labelContainer.innerHtml(label);
|
||||
});
|
||||
|
||||
inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => {
|
||||
selectBox = new SelectBox(options, selectedOption, inputCellContainer.getHTMLElement(), this._contextViewService);
|
||||
selectBox.render(inputCellContainer.getHTMLElement());
|
||||
});
|
||||
});
|
||||
return selectBox;
|
||||
}
|
||||
|
||||
private createInputBoxHelper(container: Builder, label: string, options?: IInputOptions): InputBox {
|
||||
let inputBox: InputBox;
|
||||
container.div({ class: 'dialog-input-section' }, (inputContainer) => {
|
||||
inputContainer.div({ class: 'dialog-label' }, (labelContainer) => {
|
||||
labelContainer.innerHtml(label);
|
||||
});
|
||||
|
||||
inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => {
|
||||
inputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, options);
|
||||
});
|
||||
});
|
||||
return inputBox;
|
||||
}
|
||||
|
||||
private resetRestoreContent(): void {
|
||||
this._restorePlanData.clear();
|
||||
this._fileListData.clear();
|
||||
this._restoreButton.enabled = false;
|
||||
this._scriptButton.enabled = false;
|
||||
}
|
||||
|
||||
public onValidateResponseFail(errorMessage: string) {
|
||||
this.resetRestoreContent();
|
||||
if (this.isRestoreFromDatabaseSelected) {
|
||||
this._sourceDatabaseSelectBox.showMessage({ type: MessageType.ERROR, content: errorMessage });
|
||||
} else {
|
||||
this._sourceDatabaseSelectBox.setOptions([]);
|
||||
this._filePathInputBox.showMessage({ type: MessageType.ERROR, content: errorMessage });
|
||||
}
|
||||
}
|
||||
|
||||
public removeErrorMessage() {
|
||||
this._filePathInputBox.hideMessage();
|
||||
this._sourceDatabaseSelectBox.hideMessage();
|
||||
this._destinationRestoreToInputBox.hideMessage();
|
||||
}
|
||||
|
||||
public enableRestoreButton(enabled: boolean) {
|
||||
this.hideSpinner();
|
||||
this._restoreButton.enabled = enabled;
|
||||
this._scriptButton.enabled = enabled;
|
||||
}
|
||||
|
||||
public showError(errorMessage: string): void {
|
||||
this.setError(errorMessage);
|
||||
}
|
||||
|
||||
private backupFileCheckboxChanged(e: Slick.EventData, data: Slick.OnSelectedRowsChangedEventArgs<Slick.SlickData>): void {
|
||||
let selectedFiles = [];
|
||||
data.grid.getSelectedRows().forEach(row => {
|
||||
selectedFiles.push(data.grid.getDataItem(row)['Id']);
|
||||
});
|
||||
|
||||
let isSame = false;
|
||||
if (this.viewModel.selectedBackupSets && this.viewModel.selectedBackupSets.length === selectedFiles.length) {
|
||||
isSame = this.viewModel.selectedBackupSets.some(item => selectedFiles.includes(item));
|
||||
}
|
||||
|
||||
if (!isSame) {
|
||||
this.viewModel.selectedBackupSets = selectedFiles;
|
||||
this.validateRestore(false);
|
||||
}
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
// Theme styler
|
||||
this._register(attachInputBoxStyler(this._filePathInputBox, this._themeService));
|
||||
this._register(attachInputBoxStyler(this._destinationRestoreToInputBox, this._themeService));
|
||||
this._register(attachSelectBoxStyler(this._restoreFromSelectBox, this._themeService));
|
||||
this._register(attachSelectBoxStyler(this._sourceDatabaseSelectBox, this._themeService));
|
||||
this._register(attachButtonStyler(this._browseFileButton, this._themeService));
|
||||
this._register(attachButtonStyler(this._scriptButton, this._themeService));
|
||||
this._register(attachButtonStyler(this._restoreButton, this._themeService));
|
||||
this._register(attachButtonStyler(this._closeButton, this._themeService));
|
||||
this._register(attachTableStyler(this._fileListTable, this._themeService));
|
||||
this._register(attachTableStyler(this._restorePlanTable, this._themeService));
|
||||
|
||||
this._register(this._filePathInputBox.onLoseFocus(params => {
|
||||
this.onFilePathLoseFocus(params);
|
||||
}));
|
||||
|
||||
this._register(DOM.addDisposableListener(this._browseFileButton.getElement(), DOM.EventType.CLICK, () => {
|
||||
if (this._browseFileButton.enabled) {
|
||||
this.onFileBrowserRequested();
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._sourceDatabaseSelectBox.onDidSelect(selectedDatabase => {
|
||||
this.onSourceDatabaseChanged(selectedDatabase.selected);
|
||||
}));
|
||||
|
||||
this._register(this._restoreFromSelectBox.onDidSelect(selectedRestoreFrom => {
|
||||
this.onRestoreFromChanged(selectedRestoreFrom.selected);
|
||||
}));
|
||||
}
|
||||
|
||||
private onFileBrowserRequested(): void {
|
||||
this._bootstrapService.fileBrowserDialogService.showDialog(this._ownerUri,
|
||||
this.viewModel.defaultBackupFolder,
|
||||
BackupConstants.fileFiltersSet,
|
||||
FileValidationConstants.restore,
|
||||
true,
|
||||
filepath => this.onFileBrowsed(filepath));
|
||||
}
|
||||
|
||||
private onFileBrowsed(filepath: string) {
|
||||
var oldFilePath = this._filePathInputBox.value;
|
||||
if (DialogHelper.isNullOrWhiteSpace(this._filePathInputBox.value)) {
|
||||
this._filePathInputBox.value = filepath;
|
||||
} else {
|
||||
this._filePathInputBox.value = this._filePathInputBox.value + ', ' + filepath;
|
||||
}
|
||||
|
||||
if (oldFilePath !== this._filePathInputBox.value) {
|
||||
this.onFilePathChanged(this._filePathInputBox.value);
|
||||
}
|
||||
}
|
||||
|
||||
private onFilePathLoseFocus(params: OnLoseFocusParams) {
|
||||
if (params.value) {
|
||||
if (params.hasChanged || (this.viewModel.filePath !== params.value)) {
|
||||
this.onFilePathChanged(params.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onFilePathChanged(filePath: string) {
|
||||
this.viewModel.filePath = filePath;
|
||||
this.viewModel.selectedBackupSets = null;
|
||||
this.validateRestore(true);
|
||||
}
|
||||
|
||||
private onSourceDatabaseChanged(selectedDatabase: string) {
|
||||
this.viewModel.sourceDatabaseName = selectedDatabase;
|
||||
this.viewModel.selectedBackupSets = null;
|
||||
this.viewModel.resetTailLogBackupFile();
|
||||
this.validateRestore(true);
|
||||
}
|
||||
|
||||
private onRestoreFromChanged(selectedRestoreFrom: string) {
|
||||
this.removeErrorMessage();
|
||||
if (selectedRestoreFrom === this._backupFileTitle) {
|
||||
this.viewModel.onRestoreFromChanged(true);
|
||||
new Builder(this._restoreFromBackupFileElement).show();
|
||||
} else {
|
||||
this.viewModel.onRestoreFromChanged(false);
|
||||
new Builder(this._restoreFromBackupFileElement).hide();
|
||||
}
|
||||
this.resetRestoreContent();
|
||||
}
|
||||
|
||||
private get isRestoreFromDatabaseSelected(): boolean {
|
||||
return this._restoreFromSelectBox.value === this._databaseTitle;
|
||||
}
|
||||
|
||||
public validateRestore(overwriteTargetDatabase: boolean = false): void {
|
||||
this.showSpinner();
|
||||
this._restoreButton.enabled = false;
|
||||
this._scriptButton.enabled = false;
|
||||
this._onValidate.fire(overwriteTargetDatabase);
|
||||
}
|
||||
|
||||
public restore(isScriptOnly: boolean): void {
|
||||
if (this._restoreButton.enabled) {
|
||||
this._onRestore.fire(isScriptOnly);
|
||||
}
|
||||
}
|
||||
|
||||
public hideError() {
|
||||
this.setError('');
|
||||
}
|
||||
|
||||
/* Overwrite esapce key behavior */
|
||||
protected onClose() {
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
/* Overwrite enter key behavior */
|
||||
protected onAccept() {
|
||||
this.restore(false);
|
||||
}
|
||||
|
||||
public cancel() {
|
||||
this._onCancel.fire();
|
||||
this.close();
|
||||
}
|
||||
|
||||
public close() {
|
||||
this.resetDialog();
|
||||
this.hide();
|
||||
this._onCloseEvent.fire();
|
||||
}
|
||||
|
||||
private resetDialog(): void {
|
||||
this.hideError();
|
||||
this._restoreFromSelectBox.selectWithOptionName(this._databaseTitle);
|
||||
this.onRestoreFromChanged(this._databaseTitle);
|
||||
this._sourceDatabaseSelectBox.select(0);
|
||||
this._panel.showTab(this._generalTabId);
|
||||
this.removeErrorMessage();
|
||||
this.resetRestoreContent();
|
||||
}
|
||||
|
||||
public open(serverName: string, ownerUri: string) {
|
||||
this.title = this._restoreTitle + ' - ' + serverName;
|
||||
this._ownerUri = ownerUri;
|
||||
|
||||
this.show();
|
||||
this._filePathInputBox.focus();
|
||||
}
|
||||
|
||||
protected layout(height?: number): void {
|
||||
// Nothing currently laid out statically in this class
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
for (var key in this._optionsMap) {
|
||||
var widget: Widget = this._optionsMap[key];
|
||||
widget.dispose();
|
||||
delete this._optionsMap[key];
|
||||
}
|
||||
}
|
||||
|
||||
private updateLastBackupTaken(value: string) {
|
||||
this._destinationRestoreToInputBox.value = value;
|
||||
}
|
||||
|
||||
private updateFilePath(value: string) {
|
||||
this._filePathInputBox.value = value;
|
||||
if (!value) {
|
||||
this._filePathInputBox.hideMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private updateSourceDatabaseName(databaseNamesParam: SouceDatabaseNamesParam) {
|
||||
// Always adding an empty name as the first item so if the selected db name is not in the list,
|
||||
// The empty string would be selected and not the first db in the list
|
||||
let dbNames: string[] = [];
|
||||
if (this.isRestoreFromDatabaseSelected && databaseNamesParam.databaseNames
|
||||
&& databaseNamesParam.databaseNames.length > 0 && databaseNamesParam.databaseNames[0] !== '') {
|
||||
dbNames = [''].concat(databaseNamesParam.databaseNames);
|
||||
} else {
|
||||
dbNames = databaseNamesParam.databaseNames;
|
||||
}
|
||||
this._sourceDatabaseSelectBox.setOptions(dbNames);
|
||||
this._sourceDatabaseSelectBox.selectWithOptionName(databaseNamesParam.selectedDatabase);
|
||||
}
|
||||
|
||||
private updateTargetDatabaseName(value: string) {
|
||||
this._databaseDropdown.value = value;
|
||||
}
|
||||
|
||||
private updateRestoreOption(optionParam: RestoreOptionParam) {
|
||||
let widget = this._optionsMap[optionParam.optionName];
|
||||
if (widget) {
|
||||
if (widget instanceof Checkbox) {
|
||||
(<Checkbox>widget).checked = optionParam.value;
|
||||
this.enableDisableWiget(widget, optionParam.isReadOnly);
|
||||
} else if (widget instanceof SelectBox) {
|
||||
(<SelectBox>widget).selectWithOptionName(optionParam.value);
|
||||
this.enableDisableWiget(widget, optionParam.isReadOnly);
|
||||
} else if (widget instanceof InputBox) {
|
||||
(<InputBox>widget).value = optionParam.value;
|
||||
this.enableDisableWiget(widget, optionParam.isReadOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enableDisableWiget(widget: Checkbox | SelectBox | InputBox, isReadOnly: boolean) {
|
||||
if (isReadOnly) {
|
||||
widget.disable();
|
||||
} else {
|
||||
widget.enable();
|
||||
}
|
||||
}
|
||||
|
||||
private updateRestoreDatabaseFiles(dbFiles: data.RestoreDatabaseFileInfo[]) {
|
||||
this._fileListData.clear();
|
||||
if (dbFiles) {
|
||||
let data = [];
|
||||
for (let i = 0; i < dbFiles.length; i++) {
|
||||
data[i] = {
|
||||
logicalFileName: dbFiles[i].logicalFileName,
|
||||
fileType: dbFiles[i].fileType,
|
||||
originalFileName: dbFiles[i].originalFileName,
|
||||
restoreAs: dbFiles[i].restoreAsFileName
|
||||
};
|
||||
}
|
||||
|
||||
this._fileListData.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
private updateBackupSetsToRestore(backupSetsToRestore: data.DatabaseFileInfo[]) {
|
||||
this._restorePlanData.clear();
|
||||
if (backupSetsToRestore && backupSetsToRestore.length > 0) {
|
||||
if (!this._restorePlanColumn) {
|
||||
let firstRow = backupSetsToRestore[0];
|
||||
this._restorePlanColumn = firstRow.properties.map(item => {
|
||||
return {
|
||||
id: item.propertyName,
|
||||
name: item.propertyDisplayName,
|
||||
field: item.propertyName
|
||||
};
|
||||
});
|
||||
|
||||
let checkboxSelectColumn = new CheckboxSelectColumn({ title: this._restoreLabel });
|
||||
this._register(attachCheckboxStyler(checkboxSelectColumn, this._themeService));
|
||||
this._restorePlanColumn.unshift(checkboxSelectColumn.getColumnDefinition());
|
||||
this._restorePlanTable.columns = this._restorePlanColumn;
|
||||
this._restorePlanTable.registerPlugin(checkboxSelectColumn);
|
||||
this._restorePlanTable.autosizeColumns();
|
||||
}
|
||||
|
||||
let data = [];
|
||||
let selectedRow = [];
|
||||
for (let i = 0; i < backupSetsToRestore.length; i++) {
|
||||
let backupFile = backupSetsToRestore[i];
|
||||
let newData = {};
|
||||
for (let j = 0; j < backupFile.properties.length; j++) {
|
||||
newData[backupFile.properties[j].propertyName] = backupFile.properties[j].propertyValueDisplayName;
|
||||
}
|
||||
data.push(newData);
|
||||
if (backupFile.isSelected) {
|
||||
selectedRow.push(i);
|
||||
}
|
||||
}
|
||||
this._restorePlanData.push(data);
|
||||
this._restorePlanTable.setSelectedRows(selectedRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import * as types from 'vs/base/common/types';
|
||||
|
||||
import { OptionsDialog } from 'sql/base/browser/ui/modal/optionsDialog';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import * as ConnectionConstants from 'sql/parts/connection/common/constants';
|
||||
import { ProviderConnectionInfo } from 'sql/parts/connection/common/providerConnectionInfo';
|
||||
import { IDisasterRecoveryService, IRestoreDialogController, TaskExecutionMode } from 'sql/parts/disasterRecovery/common/interfaces';
|
||||
import { MssqlRestoreInfo } from 'sql/parts/disasterRecovery/restore/mssqlRestoreInfo';
|
||||
import { RestoreDialog } from 'sql/parts/disasterRecovery/restore/restoreDialog';
|
||||
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
|
||||
import * as Utils from 'sql/parts/connection/common/utils';
|
||||
import * as data from 'data';
|
||||
|
||||
export class RestoreDialogController implements IRestoreDialogController {
|
||||
_serviceBrand: any;
|
||||
|
||||
private _restoreDialogs: { [provider: string]: RestoreDialog | OptionsDialog } = {};
|
||||
private _currentProvider: string;
|
||||
private _ownerUri: string;
|
||||
private _sessionId: string;
|
||||
private readonly _restoreFeature = 'Restore';
|
||||
private _optionValues: { [optionName: string]: any } = {};
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IDisasterRecoveryService private _disasterRecoveryService: IDisasterRecoveryService,
|
||||
@IConnectionManagementService private _connectionService: IConnectionManagementService,
|
||||
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService
|
||||
) {
|
||||
}
|
||||
|
||||
private handleOnRestore(isScriptOnly: boolean = false): void {
|
||||
let restoreOption = this.setRestoreOption();
|
||||
if (isScriptOnly) {
|
||||
restoreOption.taskExecutionMode = TaskExecutionMode.script;
|
||||
} else {
|
||||
restoreOption.taskExecutionMode = TaskExecutionMode.executeAndScript;
|
||||
}
|
||||
|
||||
this._disasterRecoveryService.restore(this._ownerUri, restoreOption);
|
||||
let restoreDialog = this._restoreDialogs[this._currentProvider];
|
||||
restoreDialog.close();
|
||||
}
|
||||
|
||||
private handleMssqlOnValidateFile(overwriteTargetDatabase: boolean = false): void {
|
||||
let restoreDialog = this._restoreDialogs[this._currentProvider] as RestoreDialog;
|
||||
this._disasterRecoveryService.getRestorePlan(this._ownerUri, this.setRestoreOption(overwriteTargetDatabase)).then(restorePlanResponse => {
|
||||
this._sessionId = restorePlanResponse.sessionId;
|
||||
|
||||
if (restorePlanResponse.errorMessage) {
|
||||
restoreDialog.onValidateResponseFail(restorePlanResponse.errorMessage);
|
||||
} else {
|
||||
restoreDialog.removeErrorMessage();
|
||||
restoreDialog.viewModel.onRestorePlanResponse(restorePlanResponse);
|
||||
}
|
||||
|
||||
if (restorePlanResponse.canRestore && !this.isEmptyBackupset()) {
|
||||
restoreDialog.enableRestoreButton(true);
|
||||
} else {
|
||||
restoreDialog.enableRestoreButton(false);
|
||||
}
|
||||
}, error => {
|
||||
restoreDialog.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary fix for bug #2506: Restore button not disabled when there's not backup set to restore
|
||||
* Will remove this function once there is a fix in the service (bug #2572)
|
||||
*/
|
||||
private isEmptyBackupset(): boolean {
|
||||
let restoreDialog = this._restoreDialogs[this._currentProvider] as RestoreDialog;
|
||||
if (!types.isUndefinedOrNull(restoreDialog.viewModel.selectedBackupSets) && restoreDialog.viewModel.selectedBackupSets.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private getMssqlRestoreConfigInfo(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let restoreDialog = this._restoreDialogs[this._currentProvider] as RestoreDialog;
|
||||
this._disasterRecoveryService.getRestoreConfigInfo(this._ownerUri).then(restoreConfigInfo => {
|
||||
restoreDialog.viewModel.updateOptionWithConfigInfo(restoreConfigInfo.configInfo);
|
||||
resolve();
|
||||
}, error => {
|
||||
restoreDialog.showError(error);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private setRestoreOption(overwriteTargetDatabase: boolean = false): data.RestoreInfo {
|
||||
let restoreInfo = undefined;
|
||||
|
||||
let providerId: string = this.getCurrentProviderId();
|
||||
if (providerId === ConnectionConstants.mssqlProviderName) {
|
||||
restoreInfo = new MssqlRestoreInfo();
|
||||
|
||||
if (this._sessionId) {
|
||||
restoreInfo.sessionId = this._sessionId;
|
||||
}
|
||||
|
||||
let restoreDialog = this._restoreDialogs[providerId] as RestoreDialog;
|
||||
restoreInfo.backupFilePaths = restoreDialog.viewModel.filePath;
|
||||
// todo: Need to change restoreInfo.readHeaderFromMedia when implement restore from database
|
||||
restoreInfo.readHeaderFromMedia = restoreDialog.viewModel.readHeaderFromMedia;
|
||||
restoreInfo.selectedBackupSets = restoreDialog.viewModel.selectedBackupSets;
|
||||
|
||||
if (restoreDialog.viewModel.sourceDatabaseName) {
|
||||
restoreInfo.sourceDatabaseName = restoreDialog.viewModel.sourceDatabaseName;
|
||||
}
|
||||
if (restoreDialog.viewModel.targetDatabaseName) {
|
||||
restoreInfo.targetDatabaseName = restoreDialog.viewModel.targetDatabaseName;
|
||||
}
|
||||
restoreInfo.overwriteTargetDatabase = overwriteTargetDatabase;
|
||||
|
||||
// Set other restore options
|
||||
restoreDialog.viewModel.getRestoreAdvancedOptions(restoreInfo.options);
|
||||
} else {
|
||||
restoreInfo = { options: this._optionValues };
|
||||
}
|
||||
|
||||
return restoreInfo;
|
||||
}
|
||||
|
||||
private getRestoreOption(): data.ServiceOption[] {
|
||||
let options: data.ServiceOption[] = [];
|
||||
let providerId: string = this.getCurrentProviderId();
|
||||
let providerCapabilities = this._capabilitiesService.getCapabilities().find(c => c.providerName === providerId);
|
||||
|
||||
if (providerCapabilities) {
|
||||
let restoreMetadataProvider = providerCapabilities.features.find(f => f.featureName === this._restoreFeature);
|
||||
if (restoreMetadataProvider) {
|
||||
options = restoreMetadataProvider.optionsMetadata;
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
private handleOnClose(): void {
|
||||
this._connectionService.disconnect(this._ownerUri);
|
||||
}
|
||||
|
||||
private handleOnCancel(): void {
|
||||
let restoreInfo = new MssqlRestoreInfo();
|
||||
restoreInfo.sessionId = this._sessionId;
|
||||
this._disasterRecoveryService.cancelRestorePlan(this._ownerUri, restoreInfo).then(() => {
|
||||
this._connectionService.disconnect(this._ownerUri);
|
||||
});
|
||||
}
|
||||
|
||||
public showDialog(connection: IConnectionProfile): TPromise<void> {
|
||||
return new TPromise<void>((resolve, reject) => {
|
||||
let result: void;
|
||||
|
||||
this._ownerUri = this._connectionService.getConnectionId(connection)
|
||||
+ ProviderConnectionInfo.idSeparator
|
||||
+ Utils.ConnectionUriRestoreIdAttributeName
|
||||
+ ProviderConnectionInfo.nameValueSeparator
|
||||
+ '0';
|
||||
|
||||
if (!this._connectionService.isConnected(this._ownerUri)) {
|
||||
this._connectionService.connect(connection, this._ownerUri).then(connectionResult => {
|
||||
this._sessionId = null;
|
||||
this._currentProvider = this.getCurrentProviderId();
|
||||
if (!this._restoreDialogs[this._currentProvider]) {
|
||||
let newRestoreDialog: RestoreDialog | OptionsDialog = undefined;
|
||||
if (this._currentProvider === ConnectionConstants.mssqlProviderName) {
|
||||
let provider = this._currentProvider;
|
||||
newRestoreDialog = this._instantiationService.createInstance(RestoreDialog, this.getRestoreOption());
|
||||
newRestoreDialog.onCancel(() => this.handleOnCancel());
|
||||
newRestoreDialog.onRestore((isScriptOnly) => this.handleOnRestore(isScriptOnly));
|
||||
newRestoreDialog.onValidate((overwriteTargetDatabase) => this.handleMssqlOnValidateFile(overwriteTargetDatabase));
|
||||
newRestoreDialog.onDatabaseListFocused(() => this.fetchDatabases(provider));
|
||||
} else {
|
||||
newRestoreDialog = this._instantiationService.createInstance(
|
||||
OptionsDialog, 'Restore database - ' + connection.serverName + ':' + connection.databaseName, 'RestoreOptions', undefined);
|
||||
newRestoreDialog.onOk(() => this.handleOnRestore());
|
||||
}
|
||||
newRestoreDialog.onCloseEvent(() => this.handleOnClose());
|
||||
newRestoreDialog.render();
|
||||
this._restoreDialogs[this._currentProvider] = newRestoreDialog;
|
||||
}
|
||||
|
||||
if (this._currentProvider === ConnectionConstants.mssqlProviderName) {
|
||||
let restoreDialog = this._restoreDialogs[this._currentProvider] as RestoreDialog;
|
||||
restoreDialog.viewModel.resetRestoreOptions(connection.databaseName);
|
||||
this.getMssqlRestoreConfigInfo().then(() => {
|
||||
restoreDialog.open(connection.serverName, this._ownerUri);
|
||||
restoreDialog.validateRestore();
|
||||
}, restoreConfigError => {
|
||||
reject(restoreConfigError);
|
||||
});
|
||||
|
||||
} else {
|
||||
let restoreDialog = this._restoreDialogs[this._currentProvider] as OptionsDialog;
|
||||
restoreDialog.open(this.getRestoreOption(), this._optionValues);
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
}, error => {
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getCurrentProviderId(): string {
|
||||
return this._connectionService.getProviderIdFromUri(this._ownerUri);
|
||||
}
|
||||
|
||||
private fetchDatabases(provider: string): void {
|
||||
this._connectionService.listDatabases(this._ownerUri).then(result => {
|
||||
if (result && result.databaseNames) {
|
||||
(<RestoreDialog> this._restoreDialogs[provider]).databaseListOptions = result.databaseNames;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
309
src/sql/parts/disasterRecovery/restore/restoreViewModel.ts
Normal file
309
src/sql/parts/disasterRecovery/restore/restoreViewModel.ts
Normal file
@@ -0,0 +1,309 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as data from 'data';
|
||||
import { ServiceOptionType } from 'sql/parts/connection/common/connectionManagement';
|
||||
import * as DialogHelper from 'sql/base/browser/ui/modal/dialogHelper';
|
||||
import * as types from 'vs/base/common/types';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export interface RestoreOptionsElement {
|
||||
optionMetadata: data.ServiceOption;
|
||||
defaultValue: any;
|
||||
currentValue: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for setting the widget in the restore dialog
|
||||
*/
|
||||
export interface RestoreOptionParam {
|
||||
optionName: string;
|
||||
value: any;
|
||||
isReadOnly: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for setting the list of source database names and selected database name in the restore dialog
|
||||
*/
|
||||
export interface SouceDatabaseNamesParam {
|
||||
databaseNames: string[];
|
||||
selectedDatabase: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* View model for restore dialog
|
||||
*/
|
||||
export class RestoreViewModel {
|
||||
public filePath: string;
|
||||
public sourceDatabaseName: string;
|
||||
public targetDatabaseName: string;
|
||||
public lastBackupTaken: string;
|
||||
public databaseList: string[];
|
||||
public readHeaderFromMedia: boolean;
|
||||
public selectedBackupSets: string[];
|
||||
public defaultBackupFolder: string;
|
||||
|
||||
private _onSetLastBackupTaken = new Emitter<string>();
|
||||
public onSetLastBackupTaken: Event<string> = this._onSetLastBackupTaken.event;
|
||||
|
||||
private _onSetfilePath = new Emitter<string>();
|
||||
public onSetfilePath: Event<string> = this._onSetfilePath.event;
|
||||
|
||||
private _onSetSourceDatabaseNames = new Emitter<SouceDatabaseNamesParam>();
|
||||
public onSetSourceDatabaseNames: Event<SouceDatabaseNamesParam> = this._onSetSourceDatabaseNames.event;
|
||||
|
||||
private _onSetTargetDatabaseName = new Emitter<string>();
|
||||
public onSetTargetDatabaseName: Event<string> = this._onSetTargetDatabaseName.event;
|
||||
|
||||
private _onSetRestoreOption = new Emitter<RestoreOptionParam>();
|
||||
public onSetRestoreOption: Event<RestoreOptionParam> = this._onSetRestoreOption.event;
|
||||
|
||||
private _onUpdateBackupSetsToRestore = new Emitter<data.DatabaseFileInfo[]>();
|
||||
public onUpdateBackupSetsToRestore: Event<data.DatabaseFileInfo[]> = this._onUpdateBackupSetsToRestore.event;
|
||||
|
||||
private _onUpdateRestoreDatabaseFiles = new Emitter<data.RestoreDatabaseFileInfo[]>();
|
||||
public onUpdateRestoreDatabaseFiles: Event<data.RestoreDatabaseFileInfo[]> = this._onUpdateRestoreDatabaseFiles.event;
|
||||
|
||||
private _optionsMap: { [name: string]: RestoreOptionsElement } = {};
|
||||
|
||||
constructor(optionsMetadata: data.ServiceOption[]) {
|
||||
optionsMetadata.forEach(optionMetadata => {
|
||||
let defaultValue = this.getDisplayValue(optionMetadata, optionMetadata.defaultValue);
|
||||
this._optionsMap[optionMetadata.name] = {
|
||||
optionMetadata: optionMetadata,
|
||||
defaultValue: defaultValue,
|
||||
currentValue: defaultValue
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get option display value
|
||||
*/
|
||||
public getDisplayValue(optionMetadata: data.ServiceOption, optionValue: any): any {
|
||||
let displayValue: any;
|
||||
switch (optionMetadata.valueType) {
|
||||
case ServiceOptionType.boolean:
|
||||
displayValue = DialogHelper.getBooleanValueFromStringOrBoolean(optionValue);
|
||||
break;
|
||||
case ServiceOptionType.category:
|
||||
let optionName = optionValue;
|
||||
if (!optionName && optionMetadata.categoryValues[0]) {
|
||||
optionName = optionMetadata.categoryValues[0].name;
|
||||
}
|
||||
displayValue = DialogHelper.getCategoryDisplayName(optionMetadata.categoryValues, optionName);
|
||||
break;
|
||||
case ServiceOptionType.string:
|
||||
displayValue = optionValue ? optionValue : '';
|
||||
}
|
||||
return displayValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* On restore from changed set readHeaderFromMedia and reset the source database names and selected database name based on isFromBackupFile value.
|
||||
*/
|
||||
public onRestoreFromChanged(isFromBackupFile: boolean) {
|
||||
this.readHeaderFromMedia = isFromBackupFile;
|
||||
if (isFromBackupFile) {
|
||||
this.updateFilePath('');
|
||||
this.updateSourceDatabaseNames([], '');
|
||||
} else {
|
||||
this.updateSourceDatabaseNames(this.databaseList, this.databaseList[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get option metadata from the option map
|
||||
*/
|
||||
public getOptionMetadata(optionName: string): data.ServiceOption {
|
||||
return this._optionsMap[optionName] ? this._optionsMap[optionName].optionMetadata : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current value for restore option
|
||||
*/
|
||||
public setOptionValue(optionName: string, value: any): void {
|
||||
if (this._optionsMap[optionName]) {
|
||||
this._optionsMap[optionName].currentValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset tail log backup file (temporary fix for bug#2838)
|
||||
*/
|
||||
public resetTailLogBackupFile(): void {
|
||||
const tailLogBackupFileOption = 'tailLogBackupFile';
|
||||
if (this._optionsMap[tailLogBackupFileOption]) {
|
||||
this._optionsMap[tailLogBackupFileOption].currentValue = null;
|
||||
this._optionsMap[tailLogBackupFileOption].defaultValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current value for restore option
|
||||
*/
|
||||
public getOptionValue(optionName: string): any {
|
||||
if (this._optionsMap[optionName]) {
|
||||
return this._optionsMap[optionName].currentValue;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get restore advanced options. Only return the options that are different from the default options
|
||||
*/
|
||||
public getRestoreAdvancedOptions(options: { [name: string]: any }) {
|
||||
for (let key in this._optionsMap) {
|
||||
let optionElement = this._optionsMap[key];
|
||||
switch (optionElement.optionMetadata.valueType) {
|
||||
case ServiceOptionType.boolean:
|
||||
if (optionElement.currentValue !== optionElement.defaultValue) {
|
||||
options[key] = optionElement.currentValue;
|
||||
}
|
||||
break;
|
||||
case ServiceOptionType.category:
|
||||
if (optionElement.currentValue !== optionElement.defaultValue) {
|
||||
options[key] = DialogHelper.getCategoryName(optionElement.optionMetadata.categoryValues, optionElement.currentValue);
|
||||
}
|
||||
break;
|
||||
case ServiceOptionType.string:
|
||||
if (optionElement.currentValue && optionElement.currentValue !== optionElement.defaultValue) {
|
||||
options[key] = optionElement.currentValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On restore plan response will update all the information from restore plan response
|
||||
*/
|
||||
public onRestorePlanResponse(restorePlanResponse: data.RestorePlanResponse): void {
|
||||
if (restorePlanResponse.planDetails && restorePlanResponse.planDetails['lastBackupTaken']) {
|
||||
this.updateLastBackupTaken(restorePlanResponse.planDetails['lastBackupTaken'].currentValue);
|
||||
}
|
||||
|
||||
if (restorePlanResponse.planDetails && restorePlanResponse.planDetails['targetDatabaseName']) {
|
||||
this.updateTargetDatabaseName(restorePlanResponse.planDetails['targetDatabaseName'].currentValue);
|
||||
}
|
||||
this._onUpdateRestoreDatabaseFiles.fire(restorePlanResponse.dbFiles);
|
||||
this.updateSourceDatabaseNames(restorePlanResponse.databaseNamesFromBackupSets, restorePlanResponse.planDetails['sourceDatabaseName'].currentValue);
|
||||
this.updateOptionWithPlanDetail(restorePlanResponse.planDetails);
|
||||
this.updateBackupSetsToRestore(restorePlanResponse.backupSetsToRestore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update options with plan details
|
||||
*/
|
||||
public updateOptionWithPlanDetail(planDetails: { [key: string]: data.RestorePlanDetailInfo }): void {
|
||||
if (planDetails) {
|
||||
for (var key in planDetails) {
|
||||
let optionElement = this._optionsMap[key];
|
||||
if (optionElement) {
|
||||
let planDetailInfo = planDetails[key];
|
||||
optionElement.defaultValue = this.getDisplayValue(optionElement.optionMetadata, planDetailInfo.defaultValue);
|
||||
optionElement.currentValue = this.getDisplayValue(optionElement.optionMetadata, planDetailInfo.currentValue);
|
||||
this._onSetRestoreOption.fire({ optionName: key, value: this._optionsMap[key].currentValue, isReadOnly: planDetailInfo.isReadOnly });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update options with restore config info. The option values will be both default and current values.
|
||||
*/
|
||||
public updateOptionWithConfigInfo(configInfo: { [key: string]: any }): void {
|
||||
if (configInfo) {
|
||||
if (configInfo['sourceDatabaseNamesWithBackupSets']) {
|
||||
let databaseList = configInfo['sourceDatabaseNamesWithBackupSets'];
|
||||
if (types.isStringArray(databaseList)) {
|
||||
this.databaseList = databaseList;
|
||||
this.databaseList.unshift('');
|
||||
this.readHeaderFromMedia = false;
|
||||
this.updateSourceDatabaseNames(this.databaseList, this.sourceDatabaseName);
|
||||
}
|
||||
}
|
||||
if (configInfo['defaultBackupFolder']) {
|
||||
this.defaultBackupFolder = configInfo['defaultBackupFolder'];
|
||||
}
|
||||
|
||||
for (var key in configInfo) {
|
||||
let optionElement = this._optionsMap[key];
|
||||
if (optionElement) {
|
||||
let planDetailInfo = configInfo[key];
|
||||
optionElement.defaultValue = this.getDisplayValue(optionElement.optionMetadata, planDetailInfo);
|
||||
optionElement.currentValue = optionElement.defaultValue;
|
||||
this._onSetRestoreOption.fire({ optionName: key, value: this._optionsMap[key].currentValue, isReadOnly: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update backup sets to restore
|
||||
*/
|
||||
public updateBackupSetsToRestore(backupSetsToRestore: data.DatabaseFileInfo[]): void {
|
||||
this.selectedBackupSets = null;
|
||||
if (backupSetsToRestore) {
|
||||
this.selectedBackupSets = [];
|
||||
backupSetsToRestore.forEach(backupFile => {
|
||||
if (backupFile.isSelected) {
|
||||
this.selectedBackupSets.push(backupFile.id);
|
||||
}
|
||||
});
|
||||
this._onUpdateBackupSetsToRestore.fire(backupSetsToRestore);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset restore options to the default value
|
||||
*/
|
||||
public resetRestoreOptions(databaseName: string): void {
|
||||
this.updateTargetDatabaseName(databaseName);
|
||||
this.updateSourceDatabaseNames([], databaseName);
|
||||
this.updateFilePath('');
|
||||
this.updateLastBackupTaken('');
|
||||
this.databaseList = [];
|
||||
this.selectedBackupSets = null;
|
||||
for (var key in this._optionsMap) {
|
||||
this._optionsMap[key].defaultValue = this.getDisplayValue(this._optionsMap[key].optionMetadata, this._optionsMap[key].optionMetadata.defaultValue);
|
||||
this._optionsMap[key].currentValue = this._optionsMap[key].defaultValue;
|
||||
this._onSetRestoreOption.fire({ optionName: key, value: this._optionsMap[key].defaultValue, isReadOnly: false });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update last backup taken
|
||||
*/
|
||||
public updateLastBackupTaken(value: string) {
|
||||
this.lastBackupTaken = value;
|
||||
this._onSetLastBackupTaken.fire(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update file path
|
||||
*/
|
||||
public updateFilePath(value: string) {
|
||||
this.filePath = value;
|
||||
this._onSetfilePath.fire(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update source database names and selected database
|
||||
*/
|
||||
public updateSourceDatabaseNames(options: string[], selectedDatabase: string) {
|
||||
this.sourceDatabaseName = selectedDatabase;
|
||||
this._onSetSourceDatabaseNames.fire({ databaseNames: options, selectedDatabase: selectedDatabase });
|
||||
}
|
||||
|
||||
/**
|
||||
* Update target database name
|
||||
*/
|
||||
public updateTargetDatabaseName(value: string) {
|
||||
this.targetDatabaseName = value;
|
||||
this._onSetTargetDatabaseName.fire(value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user