mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-03 09:35:40 -05:00
move code from parts to contrib (#8319)
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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
|
||||
}
|
||||
|
||||
.restore-panel .tabbedPanel {
|
||||
border-top-color: transparent;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { TreeViewItemHandleArg } from 'sql/workbench/common/views';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { MssqlNodeContext } from 'sql/workbench/contrib/dataExplorer/browser/mssqlNodeContext';
|
||||
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
|
||||
import { NodeType } from 'sql/workbench/contrib/objectExplorer/common/nodeType';
|
||||
import { RestoreAction } from 'sql/workbench/contrib/restore/browser/restoreActions';
|
||||
import { TreeNodeContextKey } from 'sql/workbench/contrib/objectExplorer/common/treeNodeContextKey';
|
||||
import { ObjectExplorerActionsContext } from 'sql/workbench/contrib/objectExplorer/browser/objectExplorerActions';
|
||||
import { ConnectionContextKey } from 'sql/workbench/contrib/connection/common/connectionContextKey';
|
||||
import { ManageActionContext } from 'sql/workbench/browser/actions';
|
||||
import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTreeContext';
|
||||
import { ServerInfoContextKey } from 'sql/workbench/contrib/connection/common/serverInfoContextKey';
|
||||
import { DatabaseEngineEdition } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
new RestoreAction().registerTask();
|
||||
|
||||
const DE_RESTORE_COMMAND_ID = 'dataExplorer.restore';
|
||||
// Restore
|
||||
CommandsRegistry.registerCommand({
|
||||
id: DE_RESTORE_COMMAND_ID,
|
||||
handler: (accessor, args: TreeViewItemHandleArg) => {
|
||||
const commandService = accessor.get(ICommandService);
|
||||
return commandService.executeCommand(RestoreAction.ID, args.$treeItem.payload);
|
||||
}
|
||||
});
|
||||
|
||||
// Restore Action
|
||||
MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
|
||||
group: 'connection',
|
||||
order: 5,
|
||||
command: {
|
||||
id: DE_RESTORE_COMMAND_ID,
|
||||
title: localize('restore', "Restore")
|
||||
},
|
||||
when: ContextKeyExpr.and(MssqlNodeContext.NodeProvider.isEqualTo(mssqlProviderName),
|
||||
MssqlNodeContext.NodeType.isEqualTo(NodeType.Database), MssqlNodeContext.IsCloud.toNegated(),
|
||||
MssqlNodeContext.EngineEdition.notEqualsTo(DatabaseEngineEdition.SqlOnDemand.toString()))
|
||||
});
|
||||
|
||||
// oe
|
||||
const OE_RESTORE_COMMAND_ID = 'objectExplorer.restore';
|
||||
CommandsRegistry.registerCommand({
|
||||
id: OE_RESTORE_COMMAND_ID,
|
||||
handler: (accessor, args: ObjectExplorerActionsContext) => {
|
||||
const commandService = accessor.get(ICommandService);
|
||||
return commandService.executeCommand(RestoreAction.ID, args.connectionProfile);
|
||||
}
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
|
||||
group: 'connection',
|
||||
order: 4,
|
||||
command: {
|
||||
id: OE_RESTORE_COMMAND_ID,
|
||||
title: localize('backup', "Restore")
|
||||
},
|
||||
when: ContextKeyExpr.and(TreeNodeContextKey.NodeType.isEqualTo(NodeType.Database), ConnectionContextKey.Provider.isEqualTo(mssqlProviderName),
|
||||
ServerInfoContextKey.IsCloud.toNegated(), ServerInfoContextKey.EngineEdition.notEqualsTo(DatabaseEngineEdition.SqlOnDemand.toString()))
|
||||
});
|
||||
|
||||
const ExplorerRestoreActionID = 'explorer.restore';
|
||||
CommandsRegistry.registerCommand(ExplorerRestoreActionID, (accessor, context: ManageActionContext) => {
|
||||
const commandService = accessor.get(ICommandService);
|
||||
return commandService.executeCommand(RestoreAction.ID, context.profile);
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.ExplorerWidgetContext, {
|
||||
command: {
|
||||
id: ExplorerRestoreActionID,
|
||||
title: RestoreAction.LABEL
|
||||
},
|
||||
when: ContextKeyExpr.and(ItemContextKey.ItemType.isEqualTo('database'), ItemContextKey.ConnectionProvider.isEqualTo('mssql'),
|
||||
ItemContextKey.IsCloud.toNegated(), ItemContextKey.EngineEdition.notEqualsTo(DatabaseEngineEdition.SqlOnDemand.toString())),
|
||||
order: 2
|
||||
});
|
||||
67
src/sql/workbench/contrib/restore/browser/restoreActions.ts
Normal file
67
src/sql/workbench/contrib/restore/browser/restoreActions.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { getCurrentGlobalConnection } from 'sql/workbench/browser/taskUtilities';
|
||||
import { IRestoreDialogController } from 'sql/platform/restore/common/restoreService';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
|
||||
import { Task } from 'sql/platform/tasks/browser/tasksRegistry';
|
||||
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
|
||||
export function showRestore(accessor: ServicesAccessor, connection: IConnectionProfile): Promise<void> {
|
||||
const restoreDialogService = accessor.get(IRestoreDialogController);
|
||||
return restoreDialogService.showDialog(connection).then();
|
||||
}
|
||||
|
||||
export const RestoreFeatureName = 'restore';
|
||||
|
||||
export class RestoreAction extends Task {
|
||||
public static readonly ID = RestoreFeatureName;
|
||||
public static readonly LABEL = localize('restoreAction.restore', "Restore");
|
||||
public static readonly ICON = RestoreFeatureName;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: RestoreAction.ID,
|
||||
title: RestoreAction.LABEL,
|
||||
iconPath: undefined,
|
||||
iconClass: RestoreAction.ICON
|
||||
});
|
||||
}
|
||||
|
||||
runTask(accessor: ServicesAccessor, profile: IConnectionProfile): void | Promise<void> {
|
||||
const configurationService = accessor.get<IConfigurationService>(IConfigurationService);
|
||||
const previewFeaturesEnabled: boolean = configurationService.getValue('workbench')['enablePreviewFeatures'];
|
||||
if (!previewFeaturesEnabled) {
|
||||
return accessor.get<INotificationService>(INotificationService).info(localize('restore.isPreviewFeature', "You must enable preview features in order to use restore"));
|
||||
}
|
||||
|
||||
let connectionManagementService = accessor.get<IConnectionManagementService>(IConnectionManagementService);
|
||||
if (!profile) {
|
||||
const objectExplorerService = accessor.get<IObjectExplorerService>(IObjectExplorerService);
|
||||
const workbenchEditorService = accessor.get<IEditorService>(IEditorService);
|
||||
profile = getCurrentGlobalConnection(objectExplorerService, connectionManagementService, workbenchEditorService);
|
||||
}
|
||||
if (profile) {
|
||||
const serverInfo = connectionManagementService.getServerInfo(profile.id);
|
||||
if (serverInfo && serverInfo.isCloud && profile.providerName === mssqlProviderName) {
|
||||
return accessor.get<INotificationService>(INotificationService).info(localize('restore.commandNotSupported', "Restore command is not supported for Azure SQL databases."));
|
||||
}
|
||||
}
|
||||
|
||||
const capabilitiesService = accessor.get(ICapabilitiesService);
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
profile = profile ? profile : new ConnectionProfile(capabilitiesService, profile);
|
||||
return instantiationService.invokeFunction(showRestore, profile);
|
||||
}
|
||||
}
|
||||
864
src/sql/workbench/contrib/restore/browser/restoreDialog.ts
Normal file
864
src/sql/workbench/contrib/restore/browser/restoreDialog.ts
Normal file
@@ -0,0 +1,864 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/restoreDialog';
|
||||
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
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 { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
|
||||
import { Button } from 'sql/base/browser/ui/button/button';
|
||||
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/workbench/browser/modal/dialogHelper';
|
||||
import { Modal } from 'sql/workbench/browser/modal/modal';
|
||||
import { attachButtonStyler, attachModalDialogStyler, attachTableStyler, attachInputBoxStyler, attachSelectBoxStyler, attachEditableDropdownStyler, attachCheckboxStyler, attachTabbedPanelStyler } from 'sql/platform/theme/common/styler';
|
||||
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
||||
import * as BackupConstants from 'sql/workbench/contrib/backup/common/constants';
|
||||
import { RestoreViewModel, RestoreOptionParam, SouceDatabaseNamesParam } from 'sql/workbench/contrib/restore/browser/restoreViewModel';
|
||||
import * as FileValidationConstants from 'sql/workbench/services/fileBrowser/common/fileValidationServiceConstants';
|
||||
import { Dropdown } from 'sql/base/parts/editableDropdown/browser/dropdown';
|
||||
import { TabbedPanel, PanelTabIdentifier } from 'sql/base/browser/ui/panel/panel';
|
||||
import { 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';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
|
||||
interface FileListElement {
|
||||
logicalFileName: string;
|
||||
fileType: string;
|
||||
originalFileName: string;
|
||||
restoreAs: string;
|
||||
}
|
||||
|
||||
const LocalizedStrings = {
|
||||
BACKFILEPATH: localize('backupFilePath', "Backup file path"),
|
||||
TARGETDATABASE: localize('targetDatabase', "Target database")
|
||||
};
|
||||
|
||||
export class RestoreDialog extends Modal {
|
||||
public viewModel: RestoreViewModel;
|
||||
|
||||
private _scriptButton: Button;
|
||||
private _restoreButton: Button;
|
||||
private _closeButton: Button;
|
||||
private _optionsMap: { [name: string]: SelectBox | InputBox | Checkbox } = {};
|
||||
private _restoreLabel: string;
|
||||
private _restoreTitle: string;
|
||||
private _databaseTitle: string;
|
||||
private _backupFileTitle: string;
|
||||
private _ownerUri: string;
|
||||
private _databaseDropdown: Dropdown;
|
||||
private _isBackupFileCheckboxChanged: boolean;
|
||||
|
||||
// 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 _fileListTableContainer: HTMLElement;
|
||||
|
||||
private _restorePlanTable: Table<Slick.SlickData>;
|
||||
private _restorePlanData: TableDataView<Slick.SlickData>;
|
||||
private _restorePlanColumn;
|
||||
private _restorePlanTableContainer: HTMLElement;
|
||||
private _isRenderedRestorePlanTable: boolean;
|
||||
|
||||
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: azdata.ServiceOption[],
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextViewService private _contextViewService: IContextViewService,
|
||||
@IAdsTelemetryService telemetryService: IAdsTelemetryService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IFileBrowserDialogController private fileBrowserDialogService: IFileBrowserDialogController,
|
||||
@IClipboardService clipboardService: IClipboardService,
|
||||
@ILogService logService: ILogService,
|
||||
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService
|
||||
) {
|
||||
super(localize('RestoreDialogTitle', "Restore database"), TelemetryKeys.Restore, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, { hasErrors: true, isWide: true, hasSpinner: true });
|
||||
this._restoreTitle = localize('restoreDialog.restoreTitle', "Restore database");
|
||||
this._databaseTitle = localize('restoreDialog.database', "Database");
|
||||
this._backupFileTitle = localize('restoreDialog.backupFile', "Backup file");
|
||||
this._restoreLabel = localize('restoreDialog.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));
|
||||
|
||||
this._isRenderedRestorePlanTable = false;
|
||||
}
|
||||
|
||||
public render() {
|
||||
super.render();
|
||||
attachModalDialogStyler(this, this._themeService);
|
||||
let cancelLabel = localize('restoreDialog.cancel', "Cancel");
|
||||
this._scriptButton = this.addFooterButton(localize('restoreDialog.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) {
|
||||
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._restoreFromBackupFileElement = DOM.$('.backup-file-path');
|
||||
DOM.hide(this._restoreFromBackupFileElement);
|
||||
const errorMessage = localize('missingBackupFilePathError', "Backup file path is required.");
|
||||
const 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"),
|
||||
ariaLabel: LocalizedStrings.BACKFILEPATH
|
||||
};
|
||||
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._browseFileButton = new Button(DOM.append(filePathInputContainer, DOM.$('.file-browser')));
|
||||
this._browseFileButton.label = '...';
|
||||
|
||||
const sourceDatabasesElement = DOM.$('.source-database-list');
|
||||
this._sourceDatabaseSelectBox = this.createSelectBoxHelper(sourceDatabasesElement, localize('database', "Database"), [], '');
|
||||
|
||||
// Source section
|
||||
const sourceElement = DOM.$('.source-section.new-section');
|
||||
sourceElement.append(restoreFromElement);
|
||||
sourceElement.append(this._restoreFromBackupFileElement);
|
||||
sourceElement.append(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;
|
||||
|
||||
const dropdownContainer = DOM.append(destinationInputContainer, DOM.$('.dialog-input'));
|
||||
|
||||
// Get the bootstrap params and perform the bootstrap
|
||||
dropdownContainer.style.width = '100%';
|
||||
|
||||
this._databaseDropdown = new Dropdown(dropdownContainer, this._contextViewService,
|
||||
{
|
||||
strictSelection: false,
|
||||
ariaLabel: LocalizedStrings.TARGETDATABASE,
|
||||
actionLabel: localize('restoreDialog.toggleDatabaseNameDropdown', "Select Database Toggle Dropdown")
|
||||
}
|
||||
);
|
||||
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(destinationElement, localize('restoreTo', "Restore to"));
|
||||
|
||||
// Restore plan section
|
||||
const restorePlanElement = DOM.$('.restore-plan-section.new-section');
|
||||
this.createLabelElement(restorePlanElement, localize('restorePlan', "Restore plan"), true);
|
||||
this.createLabelElement(restorePlanElement, localize('backupSetsToRestore', "Backup sets to restore"));
|
||||
|
||||
// Backup sets table
|
||||
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.setTableTitle(localize('restorePlan', "Restore plan"));
|
||||
this._restorePlanTable.setSelectionModel(new RowSelectionModel({ selectActiveRow: false }));
|
||||
this._restorePlanTable.onSelectedRowsChanged((e, data) => this.backupFileCheckboxChanged(e, data));
|
||||
|
||||
// Content in general tab
|
||||
const generalTab = DOM.$('.restore-dialog');
|
||||
generalTab.append(sourceElement);
|
||||
generalTab.append(destinationElement);
|
||||
generalTab.append(restorePlanElement);
|
||||
|
||||
// Content in file tab
|
||||
const fileContentElement = DOM.$('.restore-dialog');
|
||||
const restoreAsSectionContainer = DOM.append(fileContentElement, DOM.$('.new-section'));
|
||||
|
||||
this.createLabelElement(restoreAsSectionContainer, localize('restoreDatabaseFileAs', "Restore database files as"), true);
|
||||
this.createOptionControl(restoreAsSectionContainer, this._relocateDatabaseFilesOption);
|
||||
const subSectionContainer = DOM.append(restoreAsSectionContainer, DOM.$('.sub-section'));
|
||||
this.createOptionControl(subSectionContainer, this._relocatedDataFileFolderOption);
|
||||
this.createOptionControl(subSectionContainer, this._relocatedLogFileFolderOption);
|
||||
|
||||
// Restore database file details section
|
||||
|
||||
const restoreFileSectionContainer = DOM.append(fileContentElement, DOM.$('.new-section'));
|
||||
this.createLabelElement(restoreFileSectionContainer, localize('restoreDatabaseFileDetails', "Restore database file details"), true);
|
||||
// file list table
|
||||
this._fileListTableContainer = DOM.append(restoreFileSectionContainer, DOM.$('.dialog-input-section.restore-list'));
|
||||
DOM.hide(this._fileListTableContainer);
|
||||
const logicalFileName = localize('logicalFileName', "Logical file Name");
|
||||
const fileType = localize('fileType', "File type");
|
||||
const originalFileName = localize('originalFileName', "Original File Name");
|
||||
const restoreAs = localize('restoreAs', "Restore as");
|
||||
const 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>(this._fileListTableContainer,
|
||||
{ dataProvider: this._fileListData, columns }, { enableColumnReorder: false });
|
||||
this._fileListTable.setSelectionModel(new RowSelectionModel());
|
||||
|
||||
// Content in options tab
|
||||
const optionsContentElement = DOM.$('.restore-dialog');
|
||||
// Restore options section
|
||||
const restoreOptions = DOM.append(optionsContentElement, DOM.$('.new-section'));
|
||||
this.createLabelElement(restoreOptions, localize('restoreOptions', "Restore options"), true);
|
||||
this.createOptionControl(restoreOptions, this._withReplaceDatabaseOption);
|
||||
this.createOptionControl(restoreOptions, this._withKeepReplicationOption);
|
||||
this.createOptionControl(restoreOptions, this._withRestrictedUserOption);
|
||||
this.createOptionControl(restoreOptions, this._recoveryStateOption);
|
||||
|
||||
this.createOptionControl(DOM.append(restoreOptions, DOM.$('.sub-section')), this._standbyFileOption);
|
||||
|
||||
// Tail-Log backup section
|
||||
const tailLog = DOM.append(optionsContentElement, DOM.$('.new-section'));
|
||||
this.createLabelElement(tailLog, localize('taillogBackup', "Tail-Log backup"), true);
|
||||
this.createOptionControl(tailLog, this._takeTaillogBackupOption);
|
||||
const tailLogOptions = DOM.append(tailLog, DOM.$('.sub-section'));
|
||||
this.createOptionControl(tailLogOptions, this._tailLogWithNoRecoveryOption);
|
||||
this.createOptionControl(tailLogOptions, this._tailLogBackupFileOption);
|
||||
|
||||
// Server connections section
|
||||
const serverConnections = DOM.append(optionsContentElement, DOM.$('.new-section'));
|
||||
this.createLabelElement(serverConnections, localize('serverConnection', "Server connections"), true);
|
||||
this.createOptionControl(serverConnections, this._closeExistingConnectionsOption);
|
||||
|
||||
const restorePanel = DOM.$('.restore-panel');
|
||||
container.appendChild(restorePanel);
|
||||
this._panel = new TabbedPanel(restorePanel);
|
||||
attachTabbedPanelStyler(this._panel, this._themeService);
|
||||
this._generalTabId = this._panel.pushTab({
|
||||
identifier: 'general',
|
||||
title: localize('generalTitle', "General"),
|
||||
view: {
|
||||
render: c => {
|
||||
DOM.append(c, generalTab);
|
||||
},
|
||||
layout: () => { },
|
||||
focus: () => this._restoreFromSelectBox ? this._restoreFromSelectBox.focus() : generalTab.focus()
|
||||
}
|
||||
});
|
||||
|
||||
const fileTab = this._panel.pushTab({
|
||||
identifier: 'fileContent',
|
||||
title: localize('filesTitle', "Files"),
|
||||
view: {
|
||||
layout: () => { },
|
||||
render: c => {
|
||||
c.appendChild(fileContentElement);
|
||||
},
|
||||
focus: () => this._optionsMap[this._relocateDatabaseFilesOption] ? this._optionsMap[this._relocateDatabaseFilesOption].focus() : fileContentElement.focus()
|
||||
}
|
||||
});
|
||||
|
||||
this._panel.pushTab({
|
||||
identifier: 'options',
|
||||
title: localize('optionsTitle', "Options"),
|
||||
view: {
|
||||
layout: () => { },
|
||||
render: c => {
|
||||
c.appendChild(optionsContentElement);
|
||||
},
|
||||
focus: () => this._optionsMap[this._withReplaceDatabaseOption] ? this._optionsMap[this._withReplaceDatabaseOption].focus() : optionsContentElement.focus()
|
||||
}
|
||||
});
|
||||
|
||||
this._panel.onTabChange(c => {
|
||||
if (c === fileTab && this._fileListTable) {
|
||||
this._fileListTable.resizeCanvas();
|
||||
this._fileListTable.autosizeColumns();
|
||||
}
|
||||
if (c !== this._generalTabId) {
|
||||
this._restoreFromSelectBox.hideMessage();
|
||||
}
|
||||
});
|
||||
|
||||
this._restorePlanTable.grid.onKeyDown.subscribe((e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyMod.Shift | KeyCode.Tab)) {
|
||||
this._destinationRestoreToInputBox.isEnabled() ? this._destinationRestoreToInputBox.focus() : this._databaseDropdown.focus();
|
||||
e.stopImmediatePropagation();
|
||||
} else if (event.equals(KeyCode.Tab)) {
|
||||
this.focusOnFirstEnabledFooterButton();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
});
|
||||
|
||||
this._fileListTable.grid.onKeyDown.subscribe((e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyMod.Shift | KeyCode.Tab)) {
|
||||
if ((<InputBox>this._optionsMap[this._relocatedLogFileFolderOption]).isEnabled()) {
|
||||
(<InputBox>this._optionsMap[this._relocatedLogFileFolderOption]).focus();
|
||||
} else if ((<InputBox>this._optionsMap[this._relocatedDataFileFolderOption]).isEnabled()) {
|
||||
(<InputBox>this._optionsMap[this._relocatedDataFileFolderOption]).focus();
|
||||
} else {
|
||||
(<Checkbox>this._optionsMap[this._relocateDatabaseFilesOption]).focus();
|
||||
}
|
||||
e.stopImmediatePropagation();
|
||||
} else if (event.equals(KeyCode.Tab)) {
|
||||
this.focusOnFirstEnabledFooterButton();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private focusOnFirstEnabledFooterButton() {
|
||||
if (this._scriptButton.enabled) {
|
||||
this._scriptButton.focus();
|
||||
} else if (this._restoreButton.enabled) {
|
||||
this._restoreButton.focus();
|
||||
} else {
|
||||
this._closeButton.focus();
|
||||
}
|
||||
}
|
||||
|
||||
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: HTMLElement, content: string, isHeader?: boolean) {
|
||||
let className = 'dialog-label';
|
||||
if (isHeader) {
|
||||
className += ' header';
|
||||
}
|
||||
DOM.append(container, DOM.$(`.${className}`)).innerText = content;
|
||||
}
|
||||
|
||||
private createOptionControl(container: HTMLElement, optionName: string): void {
|
||||
const option = this.viewModel.getOptionMetadata(optionName);
|
||||
let propertyWidget: SelectBox | InputBox | Checkbox;
|
||||
switch (option.valueType) {
|
||||
case ServiceOptionType.boolean:
|
||||
propertyWidget = this.createCheckBoxHelper(container, option.description,
|
||||
DialogHelper.getBooleanValueFromStringOrBoolean(option.defaultValue), () => this.onBooleanOptionChecked(optionName));
|
||||
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: HTMLElement, label: string, isChecked: boolean, onCheck: (viaKeyboard: boolean) => void): Checkbox {
|
||||
const checkbox = 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;
|
||||
}
|
||||
|
||||
private createSelectBoxHelper(container: HTMLElement, label: string, options: string[], selectedOption: string): SelectBox {
|
||||
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 });
|
||||
selectBox.render(inputCellContainer);
|
||||
return selectBox;
|
||||
}
|
||||
|
||||
private createInputBoxHelper(container: HTMLElement, label: string, options?: IInputOptions): InputBox {
|
||||
const ariaOptions = {
|
||||
ariaLabel: label
|
||||
};
|
||||
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));
|
||||
}
|
||||
|
||||
private clearRestorePlanDataTable(): void {
|
||||
if (this._restorePlanData.getLength() > 0) {
|
||||
this._restorePlanData.clear();
|
||||
DOM.hide(this._restorePlanTableContainer);
|
||||
}
|
||||
}
|
||||
|
||||
private clearFileListTable(): void {
|
||||
if (this._fileListData.getLength() > 0) {
|
||||
this._fileListData.clear();
|
||||
DOM.hide(this._fileListTableContainer);
|
||||
}
|
||||
}
|
||||
|
||||
private resetRestoreContent(): void {
|
||||
this.clearRestorePlanDataTable();
|
||||
this.clearFileListTable();
|
||||
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.spinner = false;
|
||||
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.some(x => x === item));
|
||||
}
|
||||
|
||||
if (!isSame) {
|
||||
this.viewModel.selectedBackupSets = selectedFiles;
|
||||
this.validateRestore(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
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._browseFileButton.onDidClick(() => {
|
||||
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.fileBrowserDialogService.showDialog(this._ownerUri,
|
||||
this.viewModel.defaultBackupFolder,
|
||||
BackupConstants.fileFiltersSet,
|
||||
FileValidationConstants.restore,
|
||||
true,
|
||||
filepath => this.onFileBrowsed(filepath));
|
||||
}
|
||||
|
||||
private onFileBrowsed(filepath: string) {
|
||||
const oldFilePath = this._filePathInputBox.value;
|
||||
if (strings.isFalsyOrWhitespace(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.validateRestore(true);
|
||||
}
|
||||
|
||||
private onRestoreFromChanged(selectedRestoreFrom: string) {
|
||||
this.removeErrorMessage();
|
||||
if (selectedRestoreFrom === this._backupFileTitle) {
|
||||
this.viewModel.onRestoreFromChanged(true);
|
||||
DOM.show(this._restoreFromBackupFileElement);
|
||||
} else {
|
||||
this.viewModel.onRestoreFromChanged(false);
|
||||
DOM.hide(this._restoreFromBackupFileElement);
|
||||
}
|
||||
this.resetRestoreContent();
|
||||
}
|
||||
|
||||
private get isRestoreFromDatabaseSelected(): boolean {
|
||||
return this._restoreFromSelectBox.value === this._databaseTitle;
|
||||
}
|
||||
|
||||
public validateRestore(overwriteTargetDatabase: boolean = false, isBackupFileCheckboxChanged: boolean = false): void {
|
||||
this._isBackupFileCheckboxChanged = isBackupFileCheckboxChanged;
|
||||
this.spinner = true;
|
||||
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._isBackupFileCheckboxChanged = false;
|
||||
this.removeErrorMessage();
|
||||
this.resetRestoreContent();
|
||||
}
|
||||
|
||||
public open(serverName: string, ownerUri: string) {
|
||||
this.title = this._restoreTitle + ' - ' + serverName;
|
||||
this._ownerUri = ownerUri;
|
||||
|
||||
this.show();
|
||||
this._restoreFromSelectBox.focus();
|
||||
}
|
||||
|
||||
protected layout(height?: number): void {
|
||||
// Nothing currently laid out statically in this class
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
for (let key in this._optionsMap) {
|
||||
let 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) {
|
||||
const 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: azdata.RestoreDatabaseFileInfo[]) {
|
||||
this.clearFileListTable();
|
||||
if (dbFiles && dbFiles.length > 0) {
|
||||
const 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
|
||||
};
|
||||
}
|
||||
DOM.show(this._fileListTableContainer);
|
||||
this._fileListData.push(data);
|
||||
|
||||
// Set data and Select the first row for the table by default
|
||||
this._fileListTable.setData(this._fileListData);
|
||||
this._fileListTable.setSelectedRows([0]);
|
||||
this._fileListTable.setActiveCell(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private updateBackupSetsToRestore(backupSetsToRestore: azdata.DatabaseFileInfo[]) {
|
||||
if (this._isBackupFileCheckboxChanged) {
|
||||
const selectedRow = [];
|
||||
for (let i = 0; i < backupSetsToRestore.length; i++) {
|
||||
if (backupSetsToRestore[i].isSelected) {
|
||||
selectedRow.push(i);
|
||||
}
|
||||
}
|
||||
this._restorePlanTable.setSelectedRows(selectedRow);
|
||||
} else {
|
||||
this.clearRestorePlanDataTable();
|
||||
if (backupSetsToRestore && backupSetsToRestore.length > 0) {
|
||||
if (!this._restorePlanColumn) {
|
||||
const firstRow = backupSetsToRestore[0];
|
||||
this._restorePlanColumn = firstRow.properties.map(item => {
|
||||
return {
|
||||
id: item.propertyName,
|
||||
name: item.propertyDisplayName,
|
||||
field: item.propertyName
|
||||
};
|
||||
});
|
||||
|
||||
const checkboxSelectColumn = new CheckboxSelectColumn({ title: this._restoreLabel, toolTip: this._restoreLabel, width: 15 });
|
||||
this._restorePlanColumn.unshift(checkboxSelectColumn.getColumnDefinition());
|
||||
this._restorePlanTable.columns = this._restorePlanColumn;
|
||||
this._restorePlanTable.registerPlugin(checkboxSelectColumn);
|
||||
this._restorePlanTable.autosizeColumns();
|
||||
}
|
||||
|
||||
const data = [];
|
||||
const selectedRow = [];
|
||||
for (let i = 0; i < backupSetsToRestore.length; i++) {
|
||||
const backupFile = backupSetsToRestore[i];
|
||||
const 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);
|
||||
}
|
||||
}
|
||||
DOM.show(this._restorePlanTableContainer);
|
||||
this._restorePlanData.push(data);
|
||||
this._restorePlanTable.setSelectedRows(selectedRow);
|
||||
this._restorePlanTable.setActiveCell(selectedRow[0], 0);
|
||||
|
||||
if (!this._isRenderedRestorePlanTable) {
|
||||
this._isRenderedRestorePlanTable = true;
|
||||
this._restorePlanTable.resizeCanvas();
|
||||
this._restorePlanTable.autosizeColumns();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
298
src/sql/workbench/contrib/restore/browser/restoreViewModel.ts
Normal file
298
src/sql/workbench/contrib/restore/browser/restoreViewModel.ts
Normal file
@@ -0,0 +1,298 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as DialogHelper from 'sql/workbench/browser/modal/dialogHelper';
|
||||
import * as types from 'vs/base/common/types';
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
export interface RestoreOptionsElement {
|
||||
optionMetadata: azdata.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<azdata.DatabaseFileInfo[]>();
|
||||
public onUpdateBackupSetsToRestore: Event<azdata.DatabaseFileInfo[]> = this._onUpdateBackupSetsToRestore.event;
|
||||
|
||||
private _onUpdateRestoreDatabaseFiles = new Emitter<azdata.RestoreDatabaseFileInfo[]>();
|
||||
public onUpdateRestoreDatabaseFiles: Event<azdata.RestoreDatabaseFileInfo[]> = this._onUpdateRestoreDatabaseFiles.event;
|
||||
|
||||
private _optionsMap: { [name: string]: RestoreOptionsElement } = {};
|
||||
|
||||
constructor(optionsMetadata: azdata.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: azdata.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([], undefined);
|
||||
} else {
|
||||
this.updateSourceDatabaseNames(this.databaseList, this.databaseList[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get option metadata from the option map
|
||||
*/
|
||||
public getOptionMetadata(optionName: string): azdata.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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: azdata.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]: azdata.RestorePlanDetailInfo }): void {
|
||||
if (planDetails) {
|
||||
for (let 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 (let 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: azdata.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.sourceDatabaseName = databaseName ? databaseName : '';
|
||||
this.updateTargetDatabaseName(databaseName);
|
||||
this.updateSourceDatabaseNames([], this.sourceDatabaseName);
|
||||
this.updateFilePath('');
|
||||
this.updateLastBackupTaken('');
|
||||
this.databaseList = [];
|
||||
this.selectedBackupSets = null;
|
||||
for (let 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