mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-22 09:35:37 -05:00
UI for the Backup/Restore Managed Instance Feature (#19244)
* Rebase from main branch * Made mssql a module * remove rpc specific stuff * Added create sas RPC call * Backup to url works now * Moved createSas RPC to the BlobService * Relocated createSas RPC from sql-dataprotocolclient to the mssql * After rebase * Removed duplicate symbol * Renamed Blob to AzureBlob and relocated CreateSasResponse to mssql extension * Removed AzureBlobProvider, removed AzureBlobService feature * renamed blob to azureblob, converted thenable to promise * Simplify API * fixes * docs update, blob to azureblob update * UI design first commit * Detected Managed Instance, trying to script backup to url * azure subscription api added, but ADS crashes * Created url dialog component and added link account * Changed backup component UI logic * Changed b/r UI, added restore from URL, detected MI from restore component * Removed mocked and added real Azure API, changed RestoreDialog UI * Added file fetching API * added create sas RPC call * Backup to url works now * Fixed some bugs * Moved createSas RPC to the BlobService * Relocated createSas RPC from sql-dataprotocolclient to the mssql * Rebase createSas changed to the backupRestoreManagedInstance * PR comments fix * Enabled backup to url for gov clouds * Replaced anchor element with Link class * Fixed pick azure account logic * Removed duplicates from eslintrc * Fixed url browser dialog * Fixed restore UI, disabled url empty url browser dropdowns, fixed backup OK button * bumped sts version * bumped sts version * Fix config * Fixed URL browser dialog UX * Backup and restore dialog fix * Referencing azure resource types directly * Scoped url browser dialog css classes * Made the url browser dialog field a local variable * moved url browser files from fileBrowser to the urlBrowser folder * Changed deviceType from number to enum * Added all device type options * Moved mssql * Added MI backup button comment * Removed unhelpful comment * Revert differential copy only backup mistake * Renamed azurebrowser to urlBrowserDialog * Localize create sas button label * Removed unnecessary spinner * Use UTC date instead of locale date * Removed * and added required flag * Use async instead of nested thens * Added target database tooltip * Using deferred promise instead of event emitter * Added error handling to the url browser dialog * Registered backup component elements * Register backup component listeners * Removed redundant setDefaultBackupPaths call * Added setBackupPathList docs * Add return types * Remove code from comment * Register restore dialog elements * Register restore dialog listeners * Pass engine edition enum instead of boolean * Capitalize enum values * DatabaseEngineEdition fix * Use DeviceType instead of number * Use deferred pointer * Add new ModalDialogName * Use constructor fields * Register URL browser dialog components * Remove unnecessary helper function * nextYear function doc and move * split registerListeners method * showDialog returns promise * Backup device type comment * Pass aria label through constructor * Fix backup button * Remove comment * Comment unsupported MI backup options * Remove one liner helper function * Restore dialog methods return types * Remove comment * JS doc format * Renamed UrlBrowserDialog to BackupRestoreUrlBrowserDialog * Moved MediaDeviceType, added PhisicalDeviceType * Reorder and rename physical device type * remove extra spaces Co-authored-by: chgagnon <chgagnon@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
d38dcc853d
commit
65ef41d53d
@@ -31,15 +31,34 @@ export class AzureAccountService implements IAzureAccountService {
|
||||
* @param ignoreErrors If true any errors are not thrown and instead collected and returned as part of the result
|
||||
* @param selectedOnly Whether to only list subscriptions the user has selected to filter to for this account
|
||||
*/
|
||||
public getSubscriptions(account: azurecore.AzureAccount, ignoreErrors?: boolean, selectedOnly?: boolean): Promise<azurecore.GetSubscriptionsResult> {
|
||||
public async getSubscriptions(account: azurecore.AzureAccount, ignoreErrors?: boolean, selectedOnly?: boolean): Promise<azurecore.GetSubscriptionsResult> {
|
||||
this.checkProxy();
|
||||
return this._proxy.getSubscriptions(account, ignoreErrors, selectedOnly);
|
||||
}
|
||||
|
||||
public async getStorageAccounts(account: azurecore.AzureAccount, subscriptions: azurecore.azureResource.AzureResourceSubscription[], ignoreErrors?: boolean): Promise<azurecore.GetStorageAccountResult> {
|
||||
this.checkProxy();
|
||||
return this._proxy.getStorageAccounts(account, subscriptions, ignoreErrors);
|
||||
}
|
||||
|
||||
public async getBlobContainers(account: azurecore.AzureAccount, subscription: azurecore.azureResource.AzureResourceSubscription, storageAccount: azurecore.azureResource.AzureGraphResource, ignoreErrors?: boolean): Promise<azurecore.GetBlobContainersResult> {
|
||||
this.checkProxy();
|
||||
return this._proxy.getBlobContainers(account, subscription, storageAccount, ignoreErrors);
|
||||
}
|
||||
|
||||
public async getBlobs(account: azurecore.AzureAccount, subscription: azurecore.azureResource.AzureResourceSubscription, storageAccount: azurecore.azureResource.AzureGraphResource, containerName: string, ignoreErrors?: boolean): Promise<azurecore.GetBlobsResult> {
|
||||
this.checkProxy();
|
||||
return this._proxy.getBlobs(account, subscription, storageAccount, containerName, ignoreErrors);
|
||||
}
|
||||
|
||||
public async getStorageAccountAccessKey(account: azurecore.AzureAccount, subscription: azurecore.azureResource.AzureResourceSubscription, storageAccount: azurecore.azureResource.AzureGraphResource, ignoreErrors?: boolean): Promise<azurecore.GetStorageAccountAccessKeyResult> {
|
||||
this.checkProxy();
|
||||
return this._proxy.getStorageAccountAccessKey(account, subscription, storageAccount, ignoreErrors);
|
||||
}
|
||||
|
||||
private checkProxy(): void {
|
||||
if (!this._proxy) {
|
||||
throw new Error('Azure Account proxy not initialized');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.url-browser-dialog {
|
||||
height: 100%;
|
||||
padding-top: 12px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.url-browser-dialog .url-table-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.url-browser-dialog .option-section {
|
||||
padding-top: 10px;
|
||||
height: 90px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.url-browser-dialog .url-input-label {
|
||||
width: 50px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.url-browser-dialog .url-input-box {
|
||||
width: 200px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./media/urlBrowserDialog';
|
||||
import { Button } from 'sql/base/browser/ui/button/button';
|
||||
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import * as DialogHelper from 'sql/workbench/browser/modal/dialogHelper';
|
||||
import { HideReason, Modal } from 'sql/workbench/browser/modal/modal';
|
||||
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
import { attachModalDialogStyler } from 'sql/workbench/common/styler';
|
||||
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||
import { Account } from 'azdata';
|
||||
import { IAccountManagementService } from 'sql/platform/accounts/common/interfaces';
|
||||
import { IAzureAccountService } from 'sql/platform/azureAccount/common/azureAccountService';
|
||||
import { azureResource } from 'azurecore';
|
||||
import { IAzureBlobService } from 'sql/platform/azureBlob/common/azureBlobService';
|
||||
import { Link } from 'vs/platform/opener/browser/link';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
|
||||
/**
|
||||
* This function adds one year to the current date and returns it in the UTC format.
|
||||
* It's used to pass an expiration date argument to the create shared access signature RPC.
|
||||
* It returns the date in the UTC format for locale time zone independence.
|
||||
* @returns next year's UTC date
|
||||
*/
|
||||
function nextYear(): string {
|
||||
const today = new Date();
|
||||
const nextYear = new Date(today.getFullYear() + 1, today.getMonth(), today.getDate());
|
||||
return nextYear.toUTCString();
|
||||
}
|
||||
|
||||
export class BackupRestoreUrlBrowserDialog extends Modal {
|
||||
|
||||
private _accounts: Account[];
|
||||
private _selectedAccount: Account;
|
||||
private _subscriptions: azureResource.AzureResourceSubscription[];
|
||||
private _selectedSubscription: azureResource.AzureResourceSubscription;
|
||||
private _storageAccounts: azureResource.AzureGraphResource[];
|
||||
private _selectedStorageAccount: azureResource.AzureGraphResource;
|
||||
private _blobContainers: azureResource.BlobContainer[];
|
||||
private _selectedBlobContainer: azureResource.BlobContainer;
|
||||
private _backupFiles: azureResource.Blob[];
|
||||
|
||||
private _ownerUri: string;
|
||||
private _body: HTMLElement;
|
||||
private _accountSelectorBox: SelectBox;
|
||||
private _tenantSelectorBox: SelectBox;
|
||||
private _subscriptionSelectorBox: SelectBox;
|
||||
private _storageAccountSelectorBox: SelectBox;
|
||||
private _blobContainerSelectorBox: SelectBox;
|
||||
private _sasInputBox: InputBox;
|
||||
private _sasButton: Button;
|
||||
private _backupFileInputBox: InputBox;
|
||||
private _backupFileSelectorBox: SelectBox;
|
||||
private _okButton: Button;
|
||||
private _cancelButton: Button;
|
||||
public onOk: Deferred<string> | undefined = new Deferred();
|
||||
|
||||
|
||||
constructor(title: string,
|
||||
private _restoreDialog: boolean,
|
||||
private _defaultBackupName: string,
|
||||
@ILayoutService layoutService: ILayoutService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextViewService private _contextViewService: IContextViewService,
|
||||
@IAdsTelemetryService telemetryService: IAdsTelemetryService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IClipboardService clipboardService: IClipboardService,
|
||||
@ILogService logService: ILogService,
|
||||
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService,
|
||||
@IAccountManagementService private _accountManagementService: IAccountManagementService,
|
||||
@IAzureAccountService private _azureAccountService: IAzureAccountService,
|
||||
@IAzureBlobService private _blobService: IAzureBlobService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService
|
||||
) {
|
||||
super(title, TelemetryKeys.ModalDialogName.UrlBrowser, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, { dialogStyle: 'flyout', hasTitleIcon: false, hasBackButton: true, hasSpinner: true });
|
||||
}
|
||||
|
||||
protected layout(height?: number): void {
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement) {
|
||||
this._body = DOM.append(container, DOM.$('.url-browser-dialog'));
|
||||
}
|
||||
|
||||
public override render() {
|
||||
super.render();
|
||||
attachModalDialogStyler(this, this._themeService);
|
||||
|
||||
if (this.backButton) {
|
||||
|
||||
this._register(this.backButton.onDidClick(() => {
|
||||
this.close();
|
||||
}));
|
||||
|
||||
this._register(attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND }));
|
||||
}
|
||||
|
||||
let tableContainer: HTMLElement = DOM.append(DOM.append(this._body, DOM.$('.option-section')), DOM.$('table.url-table-content'));
|
||||
tableContainer.setAttribute('role', 'presentation');
|
||||
|
||||
let azureAccountLabel = localize('backupRestoreUrlBrowserDialog.account', "Azure Account");
|
||||
this._accountSelectorBox = this._register(new SelectBox([''], '', this._contextViewService, null, { ariaLabel: azureAccountLabel }));
|
||||
this._accountSelectorBox.disable();
|
||||
let accountSelector = DialogHelper.appendRow(tableContainer, azureAccountLabel, 'url-input-label', 'url-input-box', null, true);
|
||||
DialogHelper.appendInputSelectBox(accountSelector, this._accountSelectorBox);
|
||||
this._accountManagementService.getAccounts().then((accounts) => this.setAccountSelectorBoxOptions(accounts)).catch((err) => {
|
||||
this.setAccountSelectorBoxOptions([]);
|
||||
onUnexpectedError(err);
|
||||
});
|
||||
|
||||
let linkAccountText = localize('backupRestoreUrlBrowserDialog.linkAccount', "Link account");
|
||||
let linkAccountButton = DialogHelper.appendRow(tableContainer, '', 'url-input-label', 'url-input-box');
|
||||
const linkAccount: Link = this._register(this._instantiationService.createInstance(Link,
|
||||
{
|
||||
label: linkAccountText,
|
||||
title: linkAccountText,
|
||||
href: ''
|
||||
},
|
||||
{
|
||||
opener: async (href: string) => {
|
||||
await this._accountManagementService.openAccountListDialog();
|
||||
this._accountManagementService.getAccounts().then((accounts) => this.setAccountSelectorBoxOptions(accounts)).catch((err) => {
|
||||
this.setAccountSelectorBoxOptions([]);
|
||||
onUnexpectedError(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
));
|
||||
linkAccountButton.appendChild(linkAccount.el);
|
||||
|
||||
let tenantLabel = localize('backupRestoreUrlBrowserDialog.tenant', "Azure AD Tenant");
|
||||
this._tenantSelectorBox = this._register(new SelectBox([], '', this._contextViewService, null, { ariaLabel: tenantLabel }));
|
||||
this._tenantSelectorBox.disable();
|
||||
let tenantSelector = DialogHelper.appendRow(tableContainer, tenantLabel, 'url-input-label', 'url-input-box', null, true);
|
||||
DialogHelper.appendInputSelectBox(tenantSelector, this._tenantSelectorBox);
|
||||
|
||||
let subscriptionLabel = localize('backupRestoreUrlBrowserDialog.subscription', "Azure subscription");
|
||||
this._subscriptionSelectorBox = this._register(new SelectBox([], '', this._contextViewService, null, { ariaLabel: subscriptionLabel }));
|
||||
this._subscriptionSelectorBox.disable();
|
||||
let subscriptionSelector = DialogHelper.appendRow(tableContainer, subscriptionLabel, 'url-input-label', 'url-input-box', null, true);
|
||||
DialogHelper.appendInputSelectBox(subscriptionSelector, this._subscriptionSelectorBox);
|
||||
|
||||
let storageAccountLabel = localize('backupRestoreUrlBrowserDialog.storageAccount', "Storage account");
|
||||
this._storageAccountSelectorBox = this._register(new SelectBox([], '', this._contextViewService, null, { ariaLabel: storageAccountLabel }));
|
||||
this._storageAccountSelectorBox.disable();
|
||||
let storageAccountSelector = DialogHelper.appendRow(tableContainer, storageAccountLabel, 'url-input-label', 'url-input-box', null, true);
|
||||
DialogHelper.appendInputSelectBox(storageAccountSelector, this._storageAccountSelectorBox);
|
||||
|
||||
let blobContainerLabel = localize('backupRestoreUrlBrowserDialog.blobContainer', "Blob container");
|
||||
this._blobContainerSelectorBox = this._register(new SelectBox([], '', this._contextViewService, null, { ariaLabel: blobContainerLabel }));
|
||||
this._blobContainerSelectorBox.disable();
|
||||
let blobContainerSelector = DialogHelper.appendRow(tableContainer, blobContainerLabel, 'url-input-label', 'url-input-box', null, true);
|
||||
DialogHelper.appendInputSelectBox(blobContainerSelector, this._blobContainerSelectorBox);
|
||||
|
||||
|
||||
let sharedAccessSignatureLabel = localize('backupRestoreUrlBrowserDialog.sharedAccessSignature', "Shared access signature generated");
|
||||
let sasInput = DialogHelper.appendRow(tableContainer, sharedAccessSignatureLabel, 'url-input-label', 'url-input-box', null, true);
|
||||
this._sasInputBox = this._register(new InputBox(sasInput, this._contextViewService, { flexibleHeight: true }));
|
||||
this._sasInputBox.disable();
|
||||
this._register(this._sasInputBox.onDidChange(() => this.enableOkButton()));
|
||||
|
||||
let sasButtonContainer = DialogHelper.appendRow(tableContainer, '', 'url-input-label', 'url-input-box');
|
||||
let sasButtonLabel = localize('backupRestoreUrlBrowserDialog.sharedAccessSignatureButton', "Create Credentials");
|
||||
this._sasButton = this._register(new Button(sasButtonContainer, { title: sasButtonLabel }));
|
||||
this._sasButton.label = sasButtonLabel;
|
||||
this._sasButton.title = sasButtonLabel;
|
||||
this._register(this._sasButton.onDidClick(e => this.generateSharedAccessSignature()));
|
||||
|
||||
let backupFileLabel = localize('backupRestoreUrlBrowserDialog.backupFile', "Backup file");
|
||||
|
||||
if (this._restoreDialog) {
|
||||
this._backupFileSelectorBox = this._register(new SelectBox([], '', this._contextViewService, null, { ariaLabel: backupFileLabel }));
|
||||
let backupFileSelector = DialogHelper.appendRow(tableContainer, backupFileLabel, 'url-input-label', 'url-input-box', null, true);
|
||||
DialogHelper.appendInputSelectBox(backupFileSelector, this._backupFileSelectorBox);
|
||||
this._backupFileSelectorBox.setOptions([]);
|
||||
this._backupFileSelectorBox.disable();
|
||||
} else {
|
||||
let fileInput = DialogHelper.appendRow(tableContainer, backupFileLabel, 'url-input-label', 'url-input-box', null, true);
|
||||
this._backupFileInputBox = this._register(new InputBox(fileInput, this._contextViewService, { flexibleHeight: true }));
|
||||
this._backupFileInputBox.value = this._defaultBackupName;
|
||||
}
|
||||
|
||||
this._okButton = this.addFooterButton(localize('fileBrowser.ok', "OK"), () => this.ok());
|
||||
this._okButton.enabled = false;
|
||||
this._cancelButton = this.addFooterButton(localize('fileBrowser.discard', "Discard"), () => this.close(), 'right', true);
|
||||
|
||||
this.registerListeners();
|
||||
this.registerThemeStylers();
|
||||
}
|
||||
|
||||
private setAccountSelectorBoxOptions(accounts: Account[]) {
|
||||
this._accounts = accounts.filter(account => !account.isStale);
|
||||
const accountDisplayNames: string[] = this._accounts.map(account => account.displayInfo.displayName);
|
||||
this._accountSelectorBox.setOptions(accountDisplayNames);
|
||||
this._accountSelectorBox.select(0);
|
||||
if (this._accounts.length === 0) {
|
||||
this._accountSelectorBox.disable();
|
||||
this.onTenantSelectorBoxChanged(0);
|
||||
} else {
|
||||
this._accountSelectorBox.enable();
|
||||
}
|
||||
}
|
||||
|
||||
private onAccountSelectorBoxChanged(checkedAccount: number) {
|
||||
if (this._accounts.length !== 0) {
|
||||
this._selectedAccount = this._accounts[checkedAccount];
|
||||
const tenants = this._selectedAccount.properties.tenants;
|
||||
const tenantsDisplayNames = tenants.map(tenant => tenant.displayName);
|
||||
this._tenantSelectorBox.setOptions(tenantsDisplayNames);
|
||||
this._tenantSelectorBox.select(0);
|
||||
if (tenantsDisplayNames.length === 0) {
|
||||
this._tenantSelectorBox.disable();
|
||||
} else {
|
||||
this._tenantSelectorBox.enable();
|
||||
}
|
||||
} else {
|
||||
this._tenantSelectorBox.setOptions([]);
|
||||
this._tenantSelectorBox.select(0);
|
||||
this._tenantSelectorBox.disable();
|
||||
}
|
||||
}
|
||||
|
||||
private onTenantSelectorBoxChanged(checkedTenant: number) {
|
||||
if (this._accounts.length !== 0) {
|
||||
this._azureAccountService.getSubscriptions(this._selectedAccount)
|
||||
.then(getSubscriptionResult => this.setSubscriptionsSelectorBoxOptions(getSubscriptionResult.subscriptions))
|
||||
.catch(getSubscriptionResult => {
|
||||
this.setSubscriptionsSelectorBoxOptions([]);
|
||||
onUnexpectedError(getSubscriptionResult.errors);
|
||||
});
|
||||
} else {
|
||||
this._tenantSelectorBox.setOptions([]);
|
||||
this._tenantSelectorBox.disable();
|
||||
this.setSubscriptionsSelectorBoxOptions([]);
|
||||
}
|
||||
}
|
||||
|
||||
private setSubscriptionsSelectorBoxOptions(subscriptions: azureResource.AzureResourceSubscription[]) {
|
||||
this._subscriptions = subscriptions;
|
||||
const subscriptionDisplayNames: string[] = subscriptions.map(subscription => subscription.name);
|
||||
this._subscriptionSelectorBox.setOptions(subscriptionDisplayNames);
|
||||
this._subscriptionSelectorBox.select(0);
|
||||
if (this._subscriptions.length === 0) {
|
||||
this._subscriptionSelectorBox.disable();
|
||||
} else {
|
||||
this._subscriptionSelectorBox.enable();
|
||||
}
|
||||
}
|
||||
|
||||
private onSubscriptionSelectorBoxChanged(checkedSubscription: number) {
|
||||
if (this._subscriptions.length !== 0) {
|
||||
this._selectedSubscription = this._subscriptions[checkedSubscription];
|
||||
this._azureAccountService.getStorageAccounts(this._selectedAccount, [this._selectedSubscription])
|
||||
.then(getStorageAccountsResult => this.setStorageAccountSelectorBoxOptions(getStorageAccountsResult.resources))
|
||||
.catch(getStorageAccountsResult => {
|
||||
this.setStorageAccountSelectorBoxOptions([]);
|
||||
onUnexpectedError(getStorageAccountsResult.errors);
|
||||
});
|
||||
} else {
|
||||
this.setStorageAccountSelectorBoxOptions([]);
|
||||
}
|
||||
}
|
||||
|
||||
private setStorageAccountSelectorBoxOptions(storageAccounts: azureResource.AzureGraphResource[]) {
|
||||
this._storageAccounts = storageAccounts;
|
||||
const storageAccountDisplayNames: string[] = this._storageAccounts.map(storageAccount => storageAccount.name);
|
||||
this._storageAccountSelectorBox.setOptions(storageAccountDisplayNames);
|
||||
this._storageAccountSelectorBox.select(0);
|
||||
if (storageAccounts.length === 0) {
|
||||
this._storageAccountSelectorBox.disable();
|
||||
} else {
|
||||
this._storageAccountSelectorBox.enable();
|
||||
}
|
||||
}
|
||||
|
||||
private onStorageAccountSelectorBoxChanged(checkedStorageAccount: number) {
|
||||
if (this._storageAccounts.length !== 0) {
|
||||
this._selectedStorageAccount = this._storageAccounts[checkedStorageAccount];
|
||||
this._azureAccountService.getBlobContainers(this._selectedAccount, this._selectedSubscription, this._selectedStorageAccount)
|
||||
.then(getBlobContainersResult => this.setBlobContainersSelectorBoxOptions(getBlobContainersResult.blobContainers))
|
||||
.catch(getBlobContainersResult => {
|
||||
this.setBlobContainersSelectorBoxOptions([]);
|
||||
onUnexpectedError(getBlobContainersResult.errors);
|
||||
});
|
||||
} else {
|
||||
this.setBlobContainersSelectorBoxOptions([]);
|
||||
}
|
||||
}
|
||||
|
||||
private setBlobContainersSelectorBoxOptions(blobContainers: azureResource.BlobContainer[]) {
|
||||
this._blobContainers = blobContainers;
|
||||
const blobContainersDisplayNames: string[] = this._blobContainers.map(blobContainer => blobContainer.name);
|
||||
this._blobContainerSelectorBox.setOptions(blobContainersDisplayNames);
|
||||
this._blobContainerSelectorBox.select(0);
|
||||
if (this._blobContainers.length === 0) {
|
||||
this._blobContainerSelectorBox.disable();
|
||||
} else {
|
||||
this._blobContainerSelectorBox.enable();
|
||||
}
|
||||
}
|
||||
|
||||
private onBlobContainersSelectorBoxChanged(checkedBlobContainer: number) {
|
||||
this._sasInputBox.value = '';
|
||||
if (this._restoreDialog) {
|
||||
if (this._blobContainers.length !== 0) {
|
||||
this._selectedBlobContainer = this._blobContainers[checkedBlobContainer];
|
||||
this._azureAccountService.getBlobs(this._selectedAccount, this._selectedSubscription, this._selectedStorageAccount, this._selectedBlobContainer.name, true)
|
||||
.then(getBlobsResult => this.setBackupFilesOptions(getBlobsResult.blobs))
|
||||
.catch(getBlobsResult => {
|
||||
this.setBackupFilesOptions([]);
|
||||
onUnexpectedError(getBlobsResult.errors);
|
||||
});
|
||||
} else {
|
||||
this.setBackupFilesOptions([]);
|
||||
}
|
||||
}
|
||||
this.enableCreateCredentialsButton();
|
||||
}
|
||||
|
||||
private setBackupFilesOptions(blobs: azureResource.Blob[]) {
|
||||
this._backupFiles = blobs;
|
||||
const backupFilesDisplayNames: string[] = this._backupFiles.map(backupFile => backupFile.name);
|
||||
this._backupFileSelectorBox.setOptions(backupFilesDisplayNames);
|
||||
this._backupFileSelectorBox.select(0);
|
||||
if (this._backupFiles.length === 0) {
|
||||
this._backupFileSelectorBox.disable();
|
||||
} else {
|
||||
this._backupFileSelectorBox.enable();
|
||||
}
|
||||
}
|
||||
|
||||
public open(ownerUri: string,
|
||||
expandPath: string,
|
||||
fileFilters: [{ label: string, filters: string[] }],
|
||||
fileValidationServiceType: string,
|
||||
): void {
|
||||
this._ownerUri = ownerUri;
|
||||
this.enableOkButton();
|
||||
this.enableCreateCredentialsButton();
|
||||
this.show();
|
||||
}
|
||||
|
||||
/* enter key */
|
||||
protected override onAccept() {
|
||||
let selectedValue = this._sasInputBox.value;
|
||||
if (this._okButton.enabled === true && selectedValue !== '') {
|
||||
this.ok();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private enableOkButton() {
|
||||
if (strings.isFalsyOrWhitespace(this._blobContainerSelectorBox.value) || strings.isFalsyOrWhitespace(this._sasInputBox.value) || (this._restoreDialog && strings.isFalsyOrWhitespace(this._blobContainerSelectorBox.value)) || (!this._restoreDialog && strings.isFalsyOrWhitespace(this._backupFileInputBox.value))) {
|
||||
this._okButton.enabled = false;
|
||||
} else {
|
||||
this._okButton.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private enableCreateCredentialsButton() {
|
||||
if (strings.isFalsyOrWhitespace(this._blobContainerSelectorBox.label)) {
|
||||
this._sasButton.enabled = false;
|
||||
} else {
|
||||
this._sasButton.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private ok() {
|
||||
let returnValue = '';
|
||||
if (this._restoreDialog) {
|
||||
returnValue = `https://${this._storageAccountSelectorBox.value}.blob${this._selectedAccount.properties.providerSettings.settings.azureStorageResource.endpointSuffix}/${this._blobContainerSelectorBox.value}/${this._backupFileSelectorBox.value}`;
|
||||
} else {
|
||||
returnValue = `https://${this._storageAccountSelectorBox.value}.blob${this._selectedAccount.properties.providerSettings.settings.azureStorageResource.endpointSuffix}/${this._blobContainerSelectorBox.value}/${this._backupFileInputBox.value}`;
|
||||
}
|
||||
this.onOk.resolve(returnValue);
|
||||
this.close('ok');
|
||||
}
|
||||
|
||||
|
||||
private close(hideReason: HideReason = 'close'): void {
|
||||
this.hide(hideReason);
|
||||
}
|
||||
|
||||
private async generateSharedAccessSignature() {
|
||||
this.spinner = true;
|
||||
const blobContainerUri = `https://${this._storageAccountSelectorBox.value}.blob${this._selectedAccount.properties.providerSettings.settings.azureStorageResource.endpointSuffix}/${this._blobContainerSelectorBox.value}`;
|
||||
const getStorageAccountAccessKeyResult = await this._azureAccountService.getStorageAccountAccessKey(this._selectedAccount, this._selectedSubscription, this._selectedStorageAccount);
|
||||
const key1 = getStorageAccountAccessKeyResult.keyName1;
|
||||
const createSasResult = await this._blobService.createSas(this._ownerUri, blobContainerUri, key1, this._selectedStorageAccount.name, nextYear());
|
||||
const sas = createSasResult.sharedAccessSignature;
|
||||
this._sasInputBox.value = sas;
|
||||
this.spinner = false;
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this._accountSelectorBox.onDidSelect(e => this.onAccountSelectorBoxChanged(e.index)));
|
||||
this._register(this._tenantSelectorBox.onDidSelect(selectedTenant => this.onTenantSelectorBoxChanged(selectedTenant.index)));
|
||||
this._register(this._subscriptionSelectorBox.onDidSelect(selectedSubscription => this.onSubscriptionSelectorBoxChanged(selectedSubscription.index)));
|
||||
this._register(this._storageAccountSelectorBox.onDidSelect(selectedStorageAccount => this.onStorageAccountSelectorBoxChanged(selectedStorageAccount.index)));
|
||||
this._register(this._blobContainerSelectorBox.onDidSelect(selectedBlobContainer => {
|
||||
this.onBlobContainersSelectorBoxChanged(selectedBlobContainer.index);
|
||||
this.enableOkButton();
|
||||
}));
|
||||
|
||||
if (this._backupFileInputBox) {
|
||||
this._register(this._backupFileInputBox.onDidChange(e => this.enableOkButton()));
|
||||
}
|
||||
if (this._backupFileSelectorBox) {
|
||||
this._register(this._backupFileSelectorBox.onDidSelect(e => this.enableOkButton()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private registerThemeStylers(): void {
|
||||
this._register(attachSelectBoxStyler(this._tenantSelectorBox, this._themeService));
|
||||
this._register(attachSelectBoxStyler(this._accountSelectorBox, this._themeService));
|
||||
this._register(attachSelectBoxStyler(this._subscriptionSelectorBox, this._themeService));
|
||||
this._register(attachSelectBoxStyler(this._storageAccountSelectorBox, this._themeService));
|
||||
this._register(attachSelectBoxStyler(this._blobContainerSelectorBox, this._themeService));
|
||||
this._register(attachInputBoxStyler(this._sasInputBox, this._themeService));
|
||||
|
||||
if (this._backupFileInputBox) {
|
||||
this._register(attachInputBoxStyler(this._backupFileInputBox, this._themeService));
|
||||
}
|
||||
if (this._backupFileSelectorBox) {
|
||||
this._register(attachSelectBoxStyler(this._backupFileSelectorBox, this._themeService));
|
||||
}
|
||||
this._register(attachButtonStyler(this._sasButton, this._themeService));
|
||||
this._register(attachButtonStyler(this._okButton, this._themeService));
|
||||
this._register(attachButtonStyler(this._cancelButton, this._themeService));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { BackupRestoreUrlBrowserDialog } from 'sql/workbench/services/backupRestoreUrlBrowser/browser/urlBrowserDialog';
|
||||
import { IBackupRestoreUrlBrowserDialogService } from 'sql/workbench/services/backupRestoreUrlBrowser/common/urlBrowserDialogService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
/**
|
||||
* Url browser dialog service
|
||||
*/
|
||||
export class BackupRestoreUrlBrowserDialogService implements IBackupRestoreUrlBrowserDialogService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private _instantiationService: IInstantiationService
|
||||
) {
|
||||
}
|
||||
|
||||
public showDialog(ownerUri: string,
|
||||
expandPath: string,
|
||||
fileFilters: [{ label: string, filters: string[] }],
|
||||
fileValidationServiceType: string,
|
||||
isWide: boolean,
|
||||
isRestoreDialog: boolean,
|
||||
defaultBackupName: string
|
||||
): Promise<string> {
|
||||
const backupRestoreUrlBrowserDialog = this._instantiationService.createInstance(BackupRestoreUrlBrowserDialog, localize('filebrowser.selectBlob', "Select a blob"), isRestoreDialog, defaultBackupName);
|
||||
backupRestoreUrlBrowserDialog.render();
|
||||
|
||||
backupRestoreUrlBrowserDialog.setWide(isWide);
|
||||
backupRestoreUrlBrowserDialog.open(ownerUri, expandPath, fileFilters, fileValidationServiceType);
|
||||
return backupRestoreUrlBrowserDialog.onOk;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const IBackupRestoreUrlBrowserDialogService = createDecorator<IBackupRestoreUrlBrowserDialogService>('backupRestoreUrlBrowserDialogService');
|
||||
export interface IBackupRestoreUrlBrowserDialogService {
|
||||
_serviceBrand: undefined;
|
||||
/**
|
||||
* Show url browser dialog
|
||||
*/
|
||||
showDialog(ownerUri: string,
|
||||
expandPath: string,
|
||||
fileFilters: { label: string, filters: string[] }[],
|
||||
fileValidationServiceType: string,
|
||||
isWide: boolean,
|
||||
isRestoreDialog: boolean,
|
||||
defaultBackupName: string): Promise<string>;
|
||||
}
|
||||
@@ -34,8 +34,8 @@ import { attachTableStyler, attachInputBoxStyler, attachSelectBoxStyler, attachE
|
||||
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
||||
import { RestoreViewModel, RestoreOptionParam, SouceDatabaseNamesParam } from 'sql/workbench/services/restore/browser/restoreViewModel';
|
||||
import * as FileValidationConstants from 'sql/workbench/services/fileBrowser/common/fileValidationServiceConstants';
|
||||
import { TabbedPanel, PanelTabIdentifier } from 'sql/base/browser/ui/panel/panel';
|
||||
import { ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { IPanelTab, TabbedPanel } from 'sql/base/browser/ui/panel/panel';
|
||||
import { DatabaseEngineEdition, ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
|
||||
import { IFileBrowserDialogController } from 'sql/workbench/services/fileBrowser/common/fileBrowserDialogController';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
@@ -46,6 +46,8 @@ import { fileFiltersSet } from 'sql/workbench/services/restore/common/constants'
|
||||
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
||||
import { Dropdown } from 'sql/base/browser/ui/editableDropdown/browser/dropdown';
|
||||
import { IBackupRestoreUrlBrowserDialogService } from 'sql/workbench/services/backupRestoreUrlBrowser/common/urlBrowserDialogService';
|
||||
import { MediaDeviceType } from 'sql/workbench/contrib/backup/common/constants';
|
||||
|
||||
interface FileListElement {
|
||||
logicalFileName: string;
|
||||
@@ -55,6 +57,7 @@ interface FileListElement {
|
||||
}
|
||||
|
||||
const LocalizedStrings = {
|
||||
BACKURL: localize('backupUrl', "Backup URL"),
|
||||
BACKFILEPATH: localize('backupFilePath', "Backup file path"),
|
||||
TARGETDATABASE: localize('targetDatabase', "Target database")
|
||||
};
|
||||
@@ -70,19 +73,27 @@ export class RestoreDialog extends Modal {
|
||||
private _restoreTitle = localize('restoreDialog.restoreTitle', "Restore database");
|
||||
private _databaseTitle = localize('restoreDialog.database', "Database");
|
||||
private _backupFileTitle = localize('restoreDialog.backupFile', "Backup file");
|
||||
private _urlTitle = localize('restoreDialog.url', "URL");
|
||||
private _ownerUri?: string;
|
||||
private _databaseDropdown?: Dropdown;
|
||||
private _isBackupFileCheckboxChanged?: boolean;
|
||||
|
||||
// General options
|
||||
private _filePathInputBox?: InputBox;
|
||||
private _urlInputBox?: InputBox;
|
||||
private _browseUrlButton?: Button;
|
||||
private _browseFileButton?: Button;
|
||||
private _destinationRestoreToInputBox?: InputBox;
|
||||
private _restoreFromSelectBox?: SelectBox;
|
||||
private _sourceDatabaseSelectBox?: SelectBox;
|
||||
public _targetDatabaseInputBox: InputBox;
|
||||
|
||||
private _panel?: TabbedPanel;
|
||||
private _generalTabId?: PanelTabIdentifier;
|
||||
private _generalTab?: IPanelTab;
|
||||
private _fileTab?: IPanelTab;
|
||||
private _optionsTab?: IPanelTab;
|
||||
|
||||
private _engineEdition?: DatabaseEngineEdition;
|
||||
|
||||
// File option
|
||||
private readonly _relocateDatabaseFilesOption = 'relocateDbFiles';
|
||||
@@ -104,6 +115,11 @@ export class RestoreDialog extends Modal {
|
||||
private readonly _closeExistingConnectionsOption = 'closeExistingConnections';
|
||||
|
||||
private _restoreFromBackupFileElement?: HTMLElement;
|
||||
private _restoreFromUrlElement?: HTMLElement;
|
||||
private _destinationRestoreToContainer?: HTMLElement;
|
||||
private _sourceDatabasesElement?: HTMLElement;
|
||||
private _targetDatabaseElement?: HTMLElement;
|
||||
private _targetDatabaseInputElement?: HTMLElement;
|
||||
|
||||
private _fileListTable?: Table<FileListElement>;
|
||||
private _fileListData?: TableDataView<FileListElement>;
|
||||
@@ -138,12 +154,12 @@ export class RestoreDialog extends Modal {
|
||||
@IAdsTelemetryService telemetryService: IAdsTelemetryService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IFileBrowserDialogController private fileBrowserDialogService: IFileBrowserDialogController,
|
||||
@IBackupRestoreUrlBrowserDialogService private backupRestoreUrlBrowserDialogService: IBackupRestoreUrlBrowserDialogService,
|
||||
@IClipboardService clipboardService: IClipboardService,
|
||||
@ILogService logService: ILogService,
|
||||
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService
|
||||
) {
|
||||
super(localize('RestoreDialogTitle', "Restore database"), TelemetryKeys.ModalDialogName.Restore, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, { hasErrors: true, width: 'wide', hasSpinner: true });
|
||||
|
||||
// view model
|
||||
this.viewModel = new RestoreViewModel(optionsMetadata);
|
||||
this.viewModel.onSetLastBackupTaken((value) => this.updateLastBackupTaken(value));
|
||||
@@ -170,7 +186,31 @@ export class RestoreDialog extends Modal {
|
||||
protected renderBody(container: HTMLElement) {
|
||||
const restoreFromElement = DOM.$('.restore-from');
|
||||
this.createLabelElement(restoreFromElement, localize('source', "Source"), true);
|
||||
this._restoreFromSelectBox = this.createSelectBoxHelper(restoreFromElement, localize('restoreFrom', "Restore from"), [this._databaseTitle, this._backupFileTitle], this._databaseTitle);
|
||||
this._restoreFromSelectBox = this.createSelectBoxHelper(restoreFromElement, localize('restoreFrom', "Restore from"), [this._databaseTitle, this._backupFileTitle, this._urlTitle], this._databaseTitle);
|
||||
|
||||
this._restoreFromUrlElement = DOM.$('.backup-url');
|
||||
DOM.hide(this._restoreFromUrlElement);
|
||||
const urlErrorMessage = localize('missingBackupUrlError', "Backup url is required.");
|
||||
const urlValidationOptions: IInputOptions = {
|
||||
validationOptions: {
|
||||
validation: (value: string) => !value ? ({ type: MessageType.ERROR, content: urlErrorMessage }) : null
|
||||
},
|
||||
placeholder: localize('enterBackupUrl', "Please enter URL"),
|
||||
ariaLabel: LocalizedStrings.BACKURL
|
||||
};
|
||||
const urlInputContainer = DOM.append(this._restoreFromUrlElement, DOM.$('.dialog-input-section'));
|
||||
DOM.append(urlInputContainer, DOM.$('.dialog-label')).innerText = LocalizedStrings.BACKURL;
|
||||
|
||||
this._urlInputBox = this._register(new InputBox(DOM.append(urlInputContainer, DOM.$('.dialog-input')), this._contextViewService, urlValidationOptions));
|
||||
|
||||
const urlBrowseContainer = DOM.append(this._restoreFromUrlElement, DOM.$('.dialog-input-section'));
|
||||
DOM.append(urlBrowseContainer, DOM.$('.dialog-label')).innerText = '';
|
||||
|
||||
let browseLabel = localize('restoreDialog.browse', "Browse");
|
||||
this._browseUrlButton = this._register(new Button(DOM.append(urlBrowseContainer, DOM.$('.file-browser')), { secondary: true }));
|
||||
this._browseUrlButton.label = browseLabel;
|
||||
this._browseUrlButton.setWidth('50px');
|
||||
|
||||
|
||||
this._restoreFromBackupFileElement = DOM.$('.backup-file-path');
|
||||
DOM.hide(this._restoreFromBackupFileElement);
|
||||
@@ -185,38 +225,40 @@ export class RestoreDialog extends Modal {
|
||||
const filePathInputContainer = DOM.append(this._restoreFromBackupFileElement, DOM.$('.dialog-input-section'));
|
||||
DOM.append(filePathInputContainer, DOM.$('.dialog-label')).innerText = LocalizedStrings.BACKFILEPATH;
|
||||
|
||||
this._filePathInputBox = new InputBox(DOM.append(filePathInputContainer, DOM.$('.dialog-input')), this._contextViewService, validationOptions);
|
||||
this._filePathInputBox = this._register(new InputBox(DOM.append(filePathInputContainer, DOM.$('.dialog-input')), this._contextViewService, validationOptions));
|
||||
|
||||
this._browseFileButton = new Button(DOM.append(filePathInputContainer, DOM.$('.file-browser')), { secondary: true });
|
||||
this._browseFileButton = this._register(new Button(DOM.append(filePathInputContainer, DOM.$('.file-browser')), { secondary: true }));
|
||||
this._browseFileButton.label = '...';
|
||||
|
||||
const sourceDatabasesElement = DOM.$('.source-database-list');
|
||||
this._sourceDatabaseSelectBox = this.createSelectBoxHelper(sourceDatabasesElement, localize('database', "Database"), [], '');
|
||||
this._sourceDatabasesElement = DOM.$('.source-database-list');
|
||||
this._sourceDatabaseSelectBox = this.createSelectBoxHelper(this._sourceDatabasesElement, localize('database', "Database"), [], '');
|
||||
|
||||
// Source section
|
||||
const sourceElement = DOM.$('.source-section.new-section');
|
||||
sourceElement.append(restoreFromElement);
|
||||
sourceElement.append(this._restoreFromUrlElement);
|
||||
sourceElement.append(this._restoreFromBackupFileElement);
|
||||
sourceElement.append(sourceDatabasesElement);
|
||||
sourceElement.append(this._sourceDatabasesElement);
|
||||
|
||||
// Destination section
|
||||
const destinationElement = DOM.$('.destination-section.new-section');
|
||||
this.createLabelElement(destinationElement, localize('destination', "Destination"), true);
|
||||
|
||||
const destinationInputContainer = DOM.append(destinationElement, DOM.$('.dialog-input-section'));
|
||||
DOM.append(destinationInputContainer, DOM.$('.dialog-label')).innerText = LocalizedStrings.TARGETDATABASE;
|
||||
this._targetDatabaseElement = DOM.append(destinationElement, DOM.$('.dialog-input-section'));
|
||||
DOM.append(this._targetDatabaseElement, DOM.$('.dialog-label')).innerText = LocalizedStrings.TARGETDATABASE;
|
||||
|
||||
const dropdownContainer = DOM.append(destinationInputContainer, DOM.$('.dialog-input'));
|
||||
|
||||
const dropdownContainer = DOM.append(this._targetDatabaseElement, DOM.$('.dialog-input'));
|
||||
|
||||
// Get the bootstrap params and perform the bootstrap
|
||||
dropdownContainer.style.width = '100%';
|
||||
|
||||
this._databaseDropdown = new Dropdown(dropdownContainer, this._contextViewService,
|
||||
this._databaseDropdown = this._register(new Dropdown(dropdownContainer, this._contextViewService,
|
||||
{
|
||||
strictSelection: false,
|
||||
ariaLabel: LocalizedStrings.TARGETDATABASE
|
||||
}
|
||||
);
|
||||
));
|
||||
this._databaseDropdown.onValueChange(s => {
|
||||
this.databaseSelected(s);
|
||||
});
|
||||
@@ -232,7 +274,30 @@ export class RestoreDialog extends Modal {
|
||||
this._databaseDropdown.value = this.viewModel.targetDatabaseName!;
|
||||
attachEditableDropdownStyler(this._databaseDropdown, this._themeService);
|
||||
|
||||
this._destinationRestoreToInputBox = this.createInputBoxHelper(destinationElement, localize('restoreTo', "Restore to"));
|
||||
this._targetDatabaseInputElement = DOM.append(destinationElement, DOM.$('.dialog-input-section'));
|
||||
DOM.append(this._targetDatabaseInputElement, DOM.$('.dialog-label')).innerText = LocalizedStrings.TARGETDATABASE;
|
||||
DOM.hide(this._targetDatabaseInputElement);
|
||||
|
||||
const inputTargetDatabaseContainer = DOM.append(this._targetDatabaseInputElement, DOM.$('.dialog-input'));
|
||||
|
||||
// Get the bootstrap params and perform the bootstrap
|
||||
inputTargetDatabaseContainer.style.width = '100%';
|
||||
|
||||
this._targetDatabaseInputBox = this._register(new InputBox(inputTargetDatabaseContainer, this._contextViewService, {
|
||||
ariaLabel: LocalizedStrings.TARGETDATABASE,
|
||||
placeholder: localize('targetDatabaseTooltip', "Please enter target database name"),
|
||||
validationOptions: {
|
||||
validation: (value: string) => this.viewModel.databases.includes(value) ? ({ type: MessageType.ERROR, content: localize('restoreDialog.targetDatabaseAlreadyExists', "Target database already exists") }) : null
|
||||
},
|
||||
}));
|
||||
|
||||
const restoreToLabel = localize('restoreTo', "Restore to");
|
||||
const destinationRestoreToAriaOptions = {
|
||||
ariaLabel: restoreToLabel
|
||||
};
|
||||
this._destinationRestoreToContainer = DOM.append(destinationElement, DOM.$('.dialog-input-section'));
|
||||
DOM.append(this._destinationRestoreToContainer, DOM.$('.dialog-label')).innerText = restoreToLabel;
|
||||
this._destinationRestoreToInputBox = this._register(new InputBox(DOM.append(this._destinationRestoreToContainer, DOM.$('.dialog-input')), this._contextViewService, mixin(destinationRestoreToAriaOptions, null)));
|
||||
|
||||
// Restore plan section
|
||||
const restorePlanElement = DOM.$('.restore-plan-section.new-section');
|
||||
@@ -243,8 +308,8 @@ export class RestoreDialog extends Modal {
|
||||
this._restorePlanTableContainer = DOM.append(restorePlanElement, DOM.$('.dialog-input-section.restore-list'));
|
||||
DOM.hide(this._restorePlanTableContainer);
|
||||
this._restorePlanData = new TableDataView<Slick.SlickData>();
|
||||
this._restorePlanTable = new Table<Slick.SlickData>(this._restorePlanTableContainer,
|
||||
{ dataProvider: this._restorePlanData, columns: this._restorePlanColumn }, { enableColumnReorder: false });
|
||||
this._restorePlanTable = this._register(new Table<Slick.SlickData>(this._restorePlanTableContainer,
|
||||
{ dataProvider: this._restorePlanData, columns: this._restorePlanColumn }, { enableColumnReorder: false }));
|
||||
this._restorePlanTable.setTableTitle(localize('restorePlan', "Restore plan"));
|
||||
this._restorePlanTable.setSelectionModel(new RowSelectionModel({ selectActiveRow: false }));
|
||||
this._restorePlanTable.onSelectedRowsChanged((e, data) => this.backupFileCheckboxChanged(e, data));
|
||||
@@ -294,8 +359,8 @@ export class RestoreDialog extends Modal {
|
||||
field: 'restoreAs'
|
||||
}];
|
||||
this._fileListData = new TableDataView<FileListElement>();
|
||||
this._fileListTable = new Table<FileListElement>(this._fileListTableContainer,
|
||||
{ dataProvider: this._fileListData, columns }, { enableColumnReorder: false });
|
||||
this._fileListTable = this._register(new Table<FileListElement>(this._fileListTableContainer,
|
||||
{ dataProvider: this._fileListData, columns }, { enableColumnReorder: false }));
|
||||
this._fileListTable.setSelectionModel(new RowSelectionModel());
|
||||
|
||||
// Content in options tab
|
||||
@@ -327,7 +392,7 @@ export class RestoreDialog extends Modal {
|
||||
container.appendChild(restorePanel);
|
||||
this._panel = new TabbedPanel(restorePanel);
|
||||
attachTabbedPanelStyler(this._panel, this._themeService);
|
||||
this._generalTabId = this._panel.pushTab({
|
||||
this._generalTab = {
|
||||
identifier: 'general',
|
||||
title: localize('generalTitle', "General"),
|
||||
view: {
|
||||
@@ -336,9 +401,10 @@ export class RestoreDialog extends Modal {
|
||||
},
|
||||
layout: () => { }
|
||||
}
|
||||
});
|
||||
};
|
||||
this._panel.pushTab(this._generalTab);
|
||||
|
||||
const fileTab = this._panel.pushTab({
|
||||
this._fileTab = {
|
||||
identifier: 'fileContent',
|
||||
title: localize('filesTitle', "Files"),
|
||||
view: {
|
||||
@@ -347,9 +413,10 @@ export class RestoreDialog extends Modal {
|
||||
c.appendChild(fileContentElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
this._panel.pushTab(this._fileTab);
|
||||
|
||||
this._panel.pushTab({
|
||||
this._optionsTab = {
|
||||
identifier: 'options',
|
||||
title: localize('optionsTitle', "Options"),
|
||||
view: {
|
||||
@@ -358,17 +425,18 @@ export class RestoreDialog extends Modal {
|
||||
c.appendChild(optionsContentElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
this._panel.pushTab(this._optionsTab);
|
||||
|
||||
this._panel.onTabChange(c => {
|
||||
if (c === fileTab && this._fileListTable) {
|
||||
this._register(this._panel.onTabChange(c => {
|
||||
if (c === this._fileTab.identifier && this._fileListTable) {
|
||||
this._fileListTable.resizeCanvas();
|
||||
this._fileListTable.autosizeColumns();
|
||||
}
|
||||
if (c !== this._generalTabId) {
|
||||
if (c !== this._generalTab.identifier) {
|
||||
this._restoreFromSelectBox!.hideMessage();
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
this._restorePlanTable.grid.onKeyDown.subscribe(e => {
|
||||
let event = new StandardKeyboardEvent(<unknown>e as KeyboardEvent);
|
||||
@@ -399,7 +467,7 @@ export class RestoreDialog extends Modal {
|
||||
});
|
||||
}
|
||||
|
||||
private focusOnFirstEnabledFooterButton() {
|
||||
private focusOnFirstEnabledFooterButton(): void {
|
||||
if (this._scriptButton!.enabled) {
|
||||
this._scriptButton!.focus();
|
||||
} else if (this._restoreButton!.enabled) {
|
||||
@@ -418,9 +486,10 @@ export class RestoreDialog extends Modal {
|
||||
|
||||
public set databaseListOptions(vals: string[]) {
|
||||
this._databaseDropdown!.values = vals;
|
||||
this.viewModel.databases = vals;
|
||||
}
|
||||
|
||||
private createLabelElement(container: HTMLElement, content: string, isHeader?: boolean) {
|
||||
private createLabelElement(container: HTMLElement, content: string, isHeader?: boolean): void {
|
||||
let className = 'dialog-label';
|
||||
if (isHeader) {
|
||||
className += ' header';
|
||||
@@ -454,17 +523,17 @@ export class RestoreDialog extends Modal {
|
||||
this._optionsMap[optionName] = propertyWidget!;
|
||||
}
|
||||
|
||||
private onBooleanOptionChecked(optionName: string) {
|
||||
private onBooleanOptionChecked(optionName: string): void {
|
||||
this.viewModel.setOptionValue(optionName, (<Checkbox>this._optionsMap[optionName]).checked);
|
||||
this.validateRestore(false);
|
||||
}
|
||||
|
||||
private onCatagoryOptionChanged(optionName: string) {
|
||||
private onCatagoryOptionChanged(optionName: string): void {
|
||||
this.viewModel.setOptionValue(optionName, (<SelectBox>this._optionsMap[optionName]).value);
|
||||
this.validateRestore(false);
|
||||
}
|
||||
|
||||
private onStringOptionChanged(optionName: string, params: OnLoseFocusParams) {
|
||||
private onStringOptionChanged(optionName: string, params: OnLoseFocusParams): void {
|
||||
if (params.hasChanged && params.value) {
|
||||
this.viewModel.setOptionValue(optionName, params.value);
|
||||
this.validateRestore(false);
|
||||
@@ -472,12 +541,12 @@ export class RestoreDialog extends Modal {
|
||||
}
|
||||
|
||||
private createCheckBoxHelper(container: HTMLElement, label: string, isChecked: boolean, onCheck: (viaKeyboard: boolean) => void): Checkbox {
|
||||
const checkbox = new Checkbox(DOM.append(container, DOM.$('.dialog-input-section')), {
|
||||
const checkbox = this._register(new Checkbox(DOM.append(container, DOM.$('.dialog-input-section')), {
|
||||
label: label,
|
||||
checked: isChecked,
|
||||
onChange: onCheck,
|
||||
ariaLabel: label
|
||||
});
|
||||
}));
|
||||
this._register(attachCheckboxStyler(checkbox, this._themeService));
|
||||
return checkbox;
|
||||
}
|
||||
@@ -486,7 +555,7 @@ export class RestoreDialog extends Modal {
|
||||
const inputContainer = DOM.append(container, DOM.$('.dialog-input-section'));
|
||||
DOM.append(inputContainer, DOM.$('.dialog-label')).innerText = label;
|
||||
const inputCellContainer = DOM.append(inputContainer, DOM.$('.dialog-input'));
|
||||
const selectBox = new SelectBox(options, selectedOption, this._contextViewService, inputCellContainer, { ariaLabel: label });
|
||||
const selectBox = this._register(new SelectBox(options, selectedOption, this._contextViewService, inputCellContainer, { ariaLabel: label }));
|
||||
selectBox.render(inputCellContainer);
|
||||
return selectBox;
|
||||
}
|
||||
@@ -497,7 +566,7 @@ export class RestoreDialog extends Modal {
|
||||
};
|
||||
const inputContainer = DOM.append(container, DOM.$('.dialog-input-section'));
|
||||
DOM.append(inputContainer, DOM.$('.dialog-label')).innerText = label;
|
||||
return new InputBox(DOM.append(inputContainer, DOM.$('.dialog-input')), this._contextViewService, mixin(ariaOptions, options));
|
||||
return this._register(new InputBox(DOM.append(inputContainer, DOM.$('.dialog-input')), this._contextViewService, mixin(ariaOptions, options)));
|
||||
}
|
||||
|
||||
private clearRestorePlanDataTable(): void {
|
||||
@@ -521,7 +590,7 @@ export class RestoreDialog extends Modal {
|
||||
this._scriptButton!.enabled = false;
|
||||
}
|
||||
|
||||
public onValidateResponseFail(errorMessage: string) {
|
||||
public onValidateResponseFail(errorMessage: string): void {
|
||||
this.resetRestoreContent();
|
||||
if (this.isRestoreFromDatabaseSelected) {
|
||||
this._sourceDatabaseSelectBox!.showMessage({ type: MessageType.ERROR, content: errorMessage });
|
||||
@@ -531,16 +600,22 @@ export class RestoreDialog extends Modal {
|
||||
}
|
||||
}
|
||||
|
||||
public removeErrorMessage() {
|
||||
public removeErrorMessage(): void {
|
||||
this._filePathInputBox!.hideMessage();
|
||||
this._sourceDatabaseSelectBox!.hideMessage();
|
||||
this._destinationRestoreToInputBox!.hideMessage();
|
||||
}
|
||||
|
||||
public enableRestoreButton(enabled: boolean) {
|
||||
public enableRestoreButton(enabled: boolean): void {
|
||||
this.spinner = false;
|
||||
this._restoreButton!.enabled = enabled;
|
||||
this._scriptButton!.enabled = enabled;
|
||||
if (this._engineEdition === DatabaseEngineEdition.SqlManagedInstance && this.viewModel.databases.includes(this._targetDatabaseInputBox.value)) {
|
||||
this._restoreButton!.enabled = false;
|
||||
this._scriptButton!.enabled = false;
|
||||
}
|
||||
else {
|
||||
this._restoreButton!.enabled = enabled;
|
||||
this._scriptButton!.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public showError(errorMessage: string): void {
|
||||
@@ -549,10 +624,20 @@ export class RestoreDialog extends Modal {
|
||||
|
||||
private backupFileCheckboxChanged(e: Slick.EventData, data: Slick.OnSelectedRowsChangedEventArgs<Slick.SlickData>): void {
|
||||
let selectedFiles: string[] = [];
|
||||
let selectedDatabases: string[] = [];
|
||||
data.grid.getSelectedRows().forEach(row => {
|
||||
selectedFiles.push(data.grid.getDataItem(row)['Id']);
|
||||
selectedDatabases.push(data.grid.getDataItem(row)['Database']);
|
||||
});
|
||||
|
||||
if (selectedDatabases.length !== 0) {
|
||||
if (this._targetDatabaseInputBox.value === '') {
|
||||
this._targetDatabaseInputBox.value = selectedDatabases[0];
|
||||
}
|
||||
} else {
|
||||
this._targetDatabaseInputBox.value = '';
|
||||
}
|
||||
|
||||
let isSame = false;
|
||||
if (this.viewModel.selectedBackupSets && this.viewModel.selectedBackupSets.length === selectedFiles.length) {
|
||||
isSame = this.viewModel.selectedBackupSets.some(item => selectedFiles.some(x => x === item));
|
||||
@@ -566,24 +651,45 @@ export class RestoreDialog extends Modal {
|
||||
|
||||
private registerListeners(): void {
|
||||
// Theme styler
|
||||
this._register(attachInputBoxStyler(this._targetDatabaseInputBox, this._themeService));
|
||||
this._register(attachInputBoxStyler(this._urlInputBox!, this._themeService));
|
||||
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._browseUrlButton!, 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._targetDatabaseInputBox.onDidChange(dbName => {
|
||||
if (!this.viewModel.databases.includes(dbName)) {
|
||||
if (this.viewModel.targetDatabaseName !== dbName) {
|
||||
this.viewModel.targetDatabaseName = dbName;
|
||||
this.validateRestore();
|
||||
}
|
||||
} else {
|
||||
if (this.viewModel.targetDatabaseName !== dbName) {
|
||||
this.viewModel.targetDatabaseName = dbName;
|
||||
this.enableRestoreButton(false);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._filePathInputBox!.onLoseFocus(params => {
|
||||
this.onFilePathLoseFocus(params);
|
||||
}));
|
||||
|
||||
this._browseFileButton!.onDidClick(() => {
|
||||
this._register(this._browseFileButton!.onDidClick(() => {
|
||||
this.onFileBrowserRequested();
|
||||
});
|
||||
}));
|
||||
|
||||
this._register(this._browseUrlButton!.onDidClick(() => {
|
||||
this.onUrlBrowserRequested();
|
||||
}));
|
||||
|
||||
this._register(this._sourceDatabaseSelectBox!.onDidSelect(selectedDatabase => {
|
||||
this.onSourceDatabaseChanged(selectedDatabase.selected);
|
||||
@@ -592,6 +698,10 @@ export class RestoreDialog extends Modal {
|
||||
this._register(this._restoreFromSelectBox!.onDidSelect(selectedRestoreFrom => {
|
||||
this.onRestoreFromChanged(selectedRestoreFrom.selected);
|
||||
}));
|
||||
|
||||
this._register(this._urlInputBox!.onDidChange(url => {
|
||||
this.onUrlPathChanged(url);
|
||||
}));
|
||||
}
|
||||
|
||||
private onFileBrowserRequested(): void {
|
||||
@@ -603,7 +713,18 @@ export class RestoreDialog extends Modal {
|
||||
filepath => this.onFileBrowsed(filepath));
|
||||
}
|
||||
|
||||
private onFileBrowsed(filepath: string) {
|
||||
private onUrlBrowserRequested(): void {
|
||||
this.backupRestoreUrlBrowserDialogService.showDialog(this._ownerUri!,
|
||||
this.viewModel.defaultBackupFolder!,
|
||||
fileFiltersSet,
|
||||
FileValidationConstants.restore,
|
||||
true,
|
||||
true,
|
||||
'')
|
||||
.then(url => this._urlInputBox!.value = url);
|
||||
}
|
||||
|
||||
private onFileBrowsed(filepath: string): void {
|
||||
const oldFilePath = this._filePathInputBox!.value;
|
||||
if (strings.isFalsyOrWhitespace(this._filePathInputBox!.value)) {
|
||||
this._filePathInputBox!.value = filepath;
|
||||
@@ -616,7 +737,7 @@ export class RestoreDialog extends Modal {
|
||||
}
|
||||
}
|
||||
|
||||
private onFilePathLoseFocus(params: OnLoseFocusParams) {
|
||||
private onFilePathLoseFocus(params: OnLoseFocusParams): void {
|
||||
if (params.value) {
|
||||
if (params.hasChanged || (this.viewModel.filePath !== params.value)) {
|
||||
this.onFilePathChanged(params.value);
|
||||
@@ -624,14 +745,26 @@ export class RestoreDialog extends Modal {
|
||||
}
|
||||
}
|
||||
|
||||
private onFilePathChanged(filePath: string) {
|
||||
private onFilePathChanged(filePath: string): void {
|
||||
this.viewModel.filePath = filePath;
|
||||
this.viewModel.selectedBackupSets = undefined;
|
||||
this.validateRestore(true);
|
||||
}
|
||||
|
||||
private onSourceDatabaseChanged(selectedDatabase: string) {
|
||||
private onUrlPathChanged(urlPath: string): void {
|
||||
this.viewModel.filePath = urlPath;
|
||||
this.viewModel.selectedBackupSets = undefined;
|
||||
this.validateRestore(true);
|
||||
}
|
||||
|
||||
private onSourceDatabaseChanged(selectedDatabase: string): void {
|
||||
// This check is to avoid any unnecessary even firing (to remove flickering)
|
||||
if (this.viewModel.sourceDatabaseName === undefined) {
|
||||
this.viewModel.sourceDatabaseName = null;
|
||||
}
|
||||
if (selectedDatabase === undefined) {
|
||||
selectedDatabase = null;
|
||||
}
|
||||
if (this.viewModel.sourceDatabaseName !== selectedDatabase) {
|
||||
this.viewModel.sourceDatabaseName = selectedDatabase;
|
||||
this.viewModel.selectedBackupSets = undefined;
|
||||
@@ -639,14 +772,52 @@ export class RestoreDialog extends Modal {
|
||||
}
|
||||
}
|
||||
|
||||
private onRestoreFromChanged(selectedRestoreFrom: string) {
|
||||
private onRestoreFromChanged(selectedRestoreFrom: string): void {
|
||||
this.removeErrorMessage();
|
||||
if (selectedRestoreFrom === this._backupFileTitle) {
|
||||
this._sourceDatabaseSelectBox.enable();
|
||||
this.viewModel.onRestoreFromChanged(true);
|
||||
DOM.show(this._destinationRestoreToContainer!);
|
||||
DOM.show(this._sourceDatabasesElement!);
|
||||
DOM.show(this._restoreFromBackupFileElement!);
|
||||
} else {
|
||||
DOM.hide(this._restoreFromUrlElement);
|
||||
DOM.show(this._targetDatabaseElement!);
|
||||
DOM.hide(this._targetDatabaseInputElement!);
|
||||
if (!this._panel.contains(this._fileTab.identifier)) {
|
||||
this._panel.pushTab(this._fileTab);
|
||||
}
|
||||
if (!this._panel.contains(this._optionsTab.identifier)) {
|
||||
this._panel.pushTab(this._optionsTab);
|
||||
}
|
||||
this.viewModel.deviceType = MediaDeviceType.File;
|
||||
} else if (selectedRestoreFrom === this._databaseTitle) {
|
||||
this._sourceDatabaseSelectBox.enable();
|
||||
this.viewModel.onRestoreFromChanged(false);
|
||||
DOM.show(this._destinationRestoreToContainer!);
|
||||
DOM.show(this._sourceDatabasesElement!);
|
||||
DOM.hide(this._restoreFromBackupFileElement!);
|
||||
DOM.hide(this._restoreFromUrlElement);
|
||||
DOM.show(this._targetDatabaseElement!);
|
||||
DOM.hide(this._targetDatabaseInputElement!);
|
||||
if (!this._panel.contains(this._fileTab.identifier)) {
|
||||
this._panel.pushTab(this._fileTab);
|
||||
}
|
||||
if (!this._panel.contains(this._optionsTab.identifier)) {
|
||||
this._panel.pushTab(this._optionsTab);
|
||||
}
|
||||
this.viewModel.deviceType = MediaDeviceType.File;
|
||||
} else if (selectedRestoreFrom === this._urlTitle) {
|
||||
this.viewModel.onRestoreFromChanged(true);
|
||||
DOM.hide(this._destinationRestoreToContainer!);
|
||||
DOM.show(this._sourceDatabasesElement!);
|
||||
DOM.hide(this._restoreFromBackupFileElement!);
|
||||
DOM.show(this._restoreFromUrlElement!);
|
||||
DOM.hide(this._targetDatabaseElement!);
|
||||
DOM.show(this._targetDatabaseInputElement!);
|
||||
this._panel.removeTab(this._fileTab.identifier);
|
||||
this._panel.removeTab(this._optionsTab.identifier);
|
||||
this._databaseDropdown.value = '';
|
||||
this.viewModel.deviceType = MediaDeviceType.Url;
|
||||
}
|
||||
this.resetRestoreContent();
|
||||
}
|
||||
@@ -669,26 +840,26 @@ export class RestoreDialog extends Modal {
|
||||
}
|
||||
}
|
||||
|
||||
public hideError() {
|
||||
public hideError(): void {
|
||||
this.setError('');
|
||||
}
|
||||
|
||||
/* Overwrite esapce key behavior */
|
||||
protected override onClose() {
|
||||
protected override onClose(): void {
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
/* Overwrite enter key behavior */
|
||||
protected override onAccept() {
|
||||
protected override onAccept(): void {
|
||||
this.restore(false);
|
||||
}
|
||||
|
||||
public cancel() {
|
||||
public cancel(): void {
|
||||
this._onCancel.fire();
|
||||
this.close('cancel');
|
||||
}
|
||||
|
||||
public close(hideReason: HideReason = 'close') {
|
||||
public close(hideReason: HideReason = 'close'): void {
|
||||
this.resetDialog();
|
||||
this.hide(hideReason);
|
||||
this._onCloseEvent.fire();
|
||||
@@ -696,19 +867,38 @@ export class RestoreDialog extends Modal {
|
||||
|
||||
private resetDialog(): void {
|
||||
this.hideError();
|
||||
this._restoreFromSelectBox!.selectWithOptionName(this._databaseTitle);
|
||||
this.onRestoreFromChanged(this._databaseTitle);
|
||||
if (this._engineEdition !== DatabaseEngineEdition.SqlManagedInstance) {
|
||||
this._restoreFromSelectBox!.selectWithOptionName(this._databaseTitle);
|
||||
this.onRestoreFromChanged(this._databaseTitle);
|
||||
}
|
||||
this._sourceDatabaseSelectBox!.select(0);
|
||||
this._panel!.showTab(this._generalTabId!);
|
||||
this._panel!.showTab(this._generalTab.identifier!);
|
||||
this._isBackupFileCheckboxChanged = false;
|
||||
this.removeErrorMessage();
|
||||
this.resetRestoreContent();
|
||||
}
|
||||
|
||||
public open(serverName: string, ownerUri: string) {
|
||||
public open(serverName: string, ownerUri: string, engineEdition: DatabaseEngineEdition): void {
|
||||
this._engineEdition = engineEdition;
|
||||
this.title = this._restoreTitle + ' - ' + serverName;
|
||||
this._ownerUri = ownerUri;
|
||||
this._urlInputBox.value = '';
|
||||
this._targetDatabaseInputBox.value = '';
|
||||
let title;
|
||||
if (this._engineEdition === DatabaseEngineEdition.SqlManagedInstance) {
|
||||
this._restoreFromSelectBox.setOptions([this._urlTitle]);
|
||||
title = this._urlTitle;
|
||||
// to fetch databases
|
||||
this._onDatabaseListFocused.fire();
|
||||
this._restoreFromSelectBox.disable();
|
||||
} else {
|
||||
this._restoreFromSelectBox.setOptions([this._databaseTitle, this._backupFileTitle]);
|
||||
title = this._databaseTitle;
|
||||
this._restoreFromSelectBox.enable();
|
||||
}
|
||||
|
||||
this._restoreFromSelectBox.select(0);
|
||||
this.onRestoreFromChanged(title);
|
||||
this.show();
|
||||
this._restoreFromSelectBox!.focus();
|
||||
}
|
||||
@@ -726,18 +916,18 @@ export class RestoreDialog extends Modal {
|
||||
}
|
||||
}
|
||||
|
||||
private updateLastBackupTaken(value: string) {
|
||||
private updateLastBackupTaken(value: string): void {
|
||||
this._destinationRestoreToInputBox!.value = value;
|
||||
}
|
||||
|
||||
private updateFilePath(value: string) {
|
||||
private updateFilePath(value: string): void {
|
||||
this._filePathInputBox!.value = value;
|
||||
if (!value) {
|
||||
this._filePathInputBox!.hideMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private updateSourceDatabaseName(databaseNamesParam: SouceDatabaseNamesParam) {
|
||||
private updateSourceDatabaseName(databaseNamesParam: SouceDatabaseNamesParam): void {
|
||||
// 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[] = [];
|
||||
@@ -755,7 +945,7 @@ export class RestoreDialog extends Modal {
|
||||
this._databaseDropdown!.value = value;
|
||||
}
|
||||
|
||||
private updateRestoreOption(optionParam: RestoreOptionParam) {
|
||||
private updateRestoreOption(optionParam: RestoreOptionParam): void {
|
||||
const widget = this._optionsMap[optionParam.optionName];
|
||||
if (widget) {
|
||||
if (widget instanceof Checkbox) {
|
||||
@@ -771,7 +961,7 @@ export class RestoreDialog extends Modal {
|
||||
}
|
||||
}
|
||||
|
||||
private enableDisableWiget(widget: Checkbox | SelectBox | InputBox, isReadOnly: boolean) {
|
||||
private enableDisableWiget(widget: Checkbox | SelectBox | InputBox, isReadOnly: boolean): void {
|
||||
if (isReadOnly) {
|
||||
widget.disable();
|
||||
} else {
|
||||
@@ -779,7 +969,7 @@ export class RestoreDialog extends Modal {
|
||||
}
|
||||
}
|
||||
|
||||
private updateRestoreDatabaseFiles(dbFiles: azdata.RestoreDatabaseFileInfo[]) {
|
||||
private updateRestoreDatabaseFiles(dbFiles: azdata.RestoreDatabaseFileInfo[]): void {
|
||||
this.clearFileListTable();
|
||||
if (dbFiles && dbFiles.length > 0) {
|
||||
const data = [];
|
||||
@@ -801,7 +991,7 @@ export class RestoreDialog extends Modal {
|
||||
}
|
||||
}
|
||||
|
||||
private updateBackupSetsToRestore(backupSetsToRestore: azdata.DatabaseFileInfo[]) {
|
||||
private updateBackupSetsToRestore(backupSetsToRestore: azdata.DatabaseFileInfo[]): void {
|
||||
if (this._isBackupFileCheckboxChanged) {
|
||||
const selectedRow = [];
|
||||
for (let i = 0; i < backupSetsToRestore.length; i++) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import { IConnectionManagementService } from 'sql/platform/connection/common/con
|
||||
import { invalidProvider } from 'sql/base/common/errors';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
import { DatabaseEngineEdition } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
export class RestoreService implements IRestoreService {
|
||||
|
||||
@@ -249,6 +250,7 @@ export class RestoreDialogController implements IRestoreDialogController {
|
||||
restoreInfo.targetDatabaseName = restoreDialog.viewModel.targetDatabaseName;
|
||||
}
|
||||
restoreInfo.overwriteTargetDatabase = overwriteTargetDatabase;
|
||||
restoreInfo.deviceType = restoreDialog.viewModel.deviceType;
|
||||
|
||||
// Set other restore options
|
||||
restoreDialog.viewModel.getRestoreAdvancedOptions(restoreInfo.options);
|
||||
@@ -320,9 +322,10 @@ export class RestoreDialogController implements IRestoreDialogController {
|
||||
if (this._currentProvider === ConnectionConstants.mssqlProviderName) {
|
||||
let restoreDialog = this._restoreDialogs[this._currentProvider] as RestoreDialog;
|
||||
this.getMssqlRestoreConfigInfo().then(() => {
|
||||
const engineEdition: DatabaseEngineEdition = this._connectionService.getConnectionInfo(this._ownerUri).serverInfo.engineEditionId;
|
||||
// database list is filled only after getMssqlRestoreConfigInfo() calling before will always set to empty value
|
||||
restoreDialog.viewModel.resetRestoreOptions(connection.databaseName!, restoreDialog.viewModel.databaseList);
|
||||
restoreDialog.open(connection.serverName, this._ownerUri!);
|
||||
restoreDialog.open(connection.serverName, this._ownerUri!, engineEdition);
|
||||
restoreDialog.validateRestore();
|
||||
}, restoreConfigError => {
|
||||
reject(restoreConfigError);
|
||||
|
||||
@@ -10,6 +10,7 @@ import * as types from 'vs/base/common/types';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { MediaDeviceType } from 'sql/workbench/contrib/backup/common/constants';
|
||||
|
||||
export interface RestoreOptionsElement {
|
||||
optionMetadata: azdata.ServiceOption;
|
||||
@@ -46,6 +47,8 @@ export class RestoreViewModel {
|
||||
public readHeaderFromMedia?: boolean;
|
||||
public selectedBackupSets?: string[];
|
||||
public defaultBackupFolder?: string;
|
||||
public deviceType?: MediaDeviceType;
|
||||
public databases: string[];
|
||||
|
||||
private _onSetLastBackupTaken = new Emitter<string>();
|
||||
public onSetLastBackupTaken: Event<string> = this._onSetLastBackupTaken.event;
|
||||
|
||||
@@ -10,3 +10,4 @@ export const fileFiltersSet: { label: string, filters: string[] }[] = [
|
||||
{ label: localize('backup.filterBackupFiles', "Backup Files"), filters: ['*.bak', '*.trn', '*.log'] },
|
||||
{ label: localize('backup.allFiles', "All Files"), filters: ['*'] }
|
||||
];
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { MediaDeviceType } from 'sql/workbench/contrib/backup/common/constants';
|
||||
|
||||
export class MssqlRestoreInfo implements azdata.RestoreInfo {
|
||||
|
||||
@@ -29,6 +30,14 @@ export class MssqlRestoreInfo implements azdata.RestoreInfo {
|
||||
this.options['backupFilePaths'] = value;
|
||||
}
|
||||
|
||||
public get deviceType(): MediaDeviceType {
|
||||
return this.options['deviceType'];
|
||||
}
|
||||
|
||||
public set deviceType(value: MediaDeviceType) {
|
||||
this.options['deviceType'] = value;
|
||||
}
|
||||
|
||||
public get targetDatabaseName(): string {
|
||||
return this.options['targetDatabaseName'];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user