mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-21 12:20:29 -04:00
Add Attach Database dialog (#24225)
This commit is contained in:
188
extensions/mssql/src/objectManagement/ui/attachDatabaseDialog.ts
Normal file
188
extensions/mssql/src/objectManagement/ui/attachDatabaseDialog.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase';
|
||||
import { DatabaseFileData, IObjectManagementService, ObjectManagement } from 'mssql';
|
||||
import { Database, DatabaseViewInfo } from '../interfaces';
|
||||
import { AttachDatabaseDocUrl } from '../constants';
|
||||
import * as loc from '../localizedConstants';
|
||||
import { RemoveText } from '../../ui/localizedConstants';
|
||||
import { DefaultMinTableRowCount, DialogButton, getTableHeight } from '../../ui/dialogBase';
|
||||
import path = require('path');
|
||||
import { getErrorMessage } from '../../utils';
|
||||
|
||||
export class AttachDatabaseDialog extends ObjectManagementDialogBase<Database, DatabaseViewInfo> {
|
||||
private _databasesToAttach: DatabaseFileData[] = [];
|
||||
private _databasesTable: azdata.TableComponent;
|
||||
private _associatedFilesTable: azdata.TableComponent;
|
||||
private _databaseFiles: string[][] = [];
|
||||
private readonly _fileFilters: azdata.window.FileFilters[] = [{ label: loc.DatabaseFilesFilterLabel, filters: ['*.mdf'] }];
|
||||
|
||||
private _nameField: azdata.InputBoxComponent;
|
||||
private _nameContainer: azdata.FlexContainer;
|
||||
|
||||
private _ownerDropdown: azdata.DropDownComponent;
|
||||
private _ownerContainer: azdata.FlexContainer;
|
||||
|
||||
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
|
||||
super(objectManagementService, options, loc.AttachDatabaseDialogTitle, 'AttachDatabase');
|
||||
this.dialogObject.okButton.label = loc.AttachButtonLabel;
|
||||
}
|
||||
|
||||
protected override get isDirty(): boolean {
|
||||
return this._databasesToAttach.length > 0;
|
||||
}
|
||||
|
||||
protected async initializeUI(): Promise<void> {
|
||||
let filesSection = this.initializeAttachSection();
|
||||
let associatedSection = this.initializeAssociatedFilesSection();
|
||||
this.formContainer.addItems([filesSection, associatedSection]);
|
||||
}
|
||||
|
||||
private initializeAttachSection(): azdata.GroupContainer {
|
||||
const columns = [loc.MdfFileLocation, loc.DatabaseName];
|
||||
this._databasesTable = this.createTable(loc.DatabasesToAttachLabel, columns, []);
|
||||
this.disposables.push(this._databasesTable.onRowSelected(() => this.onFileRowSelected()))
|
||||
|
||||
let addButton: DialogButton = {
|
||||
buttonAriaLabel: loc.AddFileAriaLabel,
|
||||
buttonHandler: async () => await this.onAddFilesButtonClicked()
|
||||
};
|
||||
let removeButton: DialogButton = {
|
||||
buttonAriaLabel: RemoveText,
|
||||
buttonHandler: async () => await this.onRemoveFilesButtonClicked()
|
||||
};
|
||||
const buttonContainer = this.addButtonsForTable(this._databasesTable, addButton, removeButton);
|
||||
|
||||
this._nameField = this.createInputBox(async newValue => {
|
||||
let selectedRow = this._databasesTable.selectedRows[0];
|
||||
let dbFile = this._databasesToAttach[selectedRow];
|
||||
dbFile.databaseName = newValue;
|
||||
}, {});
|
||||
this._nameContainer = this.createLabelInputContainer(loc.AttachAsText, this._nameField);
|
||||
|
||||
this._ownerDropdown = this.createDropdown(loc.OwnerText, async newValue => {
|
||||
let selectedRow = this._databasesTable.selectedRows[0];
|
||||
let dbFile = this._databasesToAttach[selectedRow];
|
||||
dbFile.owner = newValue;
|
||||
}, this.viewInfo.loginNames.options, this.viewInfo.loginNames.options[this.viewInfo.loginNames.defaultValueIndex]);
|
||||
this._ownerContainer = this.createLabelInputContainer(loc.OwnerText, this._ownerDropdown);
|
||||
|
||||
// Hide input controls until we have files in the table
|
||||
this._nameContainer.display = 'none';
|
||||
this._ownerContainer.display = 'none';
|
||||
|
||||
return this.createGroup(loc.DatabasesToAttachLabel, [this._databasesTable, buttonContainer, this._nameContainer, this._ownerContainer], false);
|
||||
}
|
||||
|
||||
private initializeAssociatedFilesSection(): azdata.GroupContainer {
|
||||
const columns = [loc.DatabaseFileNameLabel, loc.DatabaseFileTypeLabel, loc.DatabaseFilePathLabel];
|
||||
this._associatedFilesTable = this.createTable(loc.DatabaseFilesLabel, columns, []);
|
||||
return this.createGroup(loc.AssociatedFilesLabel, [this._associatedFilesTable], false);
|
||||
}
|
||||
|
||||
private async onFileRowSelected(): Promise<void> {
|
||||
if (this._databasesTable.selectedRows?.length > 0) {
|
||||
let selectedRow = this._databasesTable.selectedRows[0];
|
||||
let dbFile = this._databasesToAttach[selectedRow];
|
||||
|
||||
this._nameField.value = dbFile.databaseName;
|
||||
this._ownerDropdown.value = dbFile.owner;
|
||||
|
||||
await this.updateAssociatedFilesTable(dbFile.databaseFilePaths);
|
||||
} else {
|
||||
await this.updateAssociatedFilesTable([]);
|
||||
}
|
||||
}
|
||||
|
||||
private async updateAssociatedFilesTable(filePaths: string[]): Promise<void> {
|
||||
let tableRows = filePaths.map(filePath => {
|
||||
let ext = path.extname(filePath);
|
||||
let fileType = ext === '.ldf' ? loc.LogFileLabel : loc.DataFileLabel;
|
||||
let fileName = path.basename(filePath, ext);
|
||||
return [fileName, fileType, filePath];
|
||||
});
|
||||
await this._associatedFilesTable.updateProperties({
|
||||
data: tableRows,
|
||||
height: getTableHeight(tableRows.length, DefaultMinTableRowCount)
|
||||
});
|
||||
}
|
||||
|
||||
private async onAddFilesButtonClicked(): Promise<void> {
|
||||
try {
|
||||
let dataFolder = await this.objectManagementService.getDataFolder(this.options.connectionUri);
|
||||
let filePath = await azdata.window.openServerFileBrowserDialog(this.options.connectionUri, dataFolder, this._fileFilters);
|
||||
if (filePath) {
|
||||
let owner = this.viewInfo.loginNames?.options[this.viewInfo.loginNames.defaultValueIndex];
|
||||
let fileName = path.basename(filePath, path.extname(filePath));
|
||||
let tableRow = [filePath, fileName];
|
||||
|
||||
// Associated files will also include the primary file, so we don't need to add it to the array again
|
||||
let associatedFiles = await this.objectManagementService.getAssociatedFiles(this.options.connectionUri, filePath) ?? [];
|
||||
|
||||
this._databaseFiles.push(tableRow);
|
||||
this._databasesToAttach.push({ databaseName: fileName, databaseFilePaths: associatedFiles, owner });
|
||||
|
||||
this._nameContainer.display = 'block';
|
||||
this._ownerContainer.display = 'block';
|
||||
|
||||
await this.updateTableData();
|
||||
this._databasesTable.setActiveCell(this._databasesToAttach.length - 1, 0);
|
||||
}
|
||||
} catch (error) {
|
||||
this.dialogObject.message = {
|
||||
text: getErrorMessage(error),
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async onRemoveFilesButtonClicked(): Promise<void> {
|
||||
let selectedRows = this._databasesTable.selectedRows;
|
||||
let deletedRowCount = 0;
|
||||
for (let row of selectedRows) {
|
||||
let index = row - deletedRowCount;
|
||||
this._databaseFiles.splice(index, 1);
|
||||
this._databasesToAttach.splice(index, 1);
|
||||
deletedRowCount++;
|
||||
}
|
||||
if (this._databasesToAttach.length === 0) {
|
||||
this._nameContainer.display = 'none';
|
||||
this._ownerContainer.display = 'none';
|
||||
} else {
|
||||
this._databasesTable.setActiveCell(0, 0);
|
||||
}
|
||||
await this.updateTableData();
|
||||
}
|
||||
|
||||
private async updateTableData(): Promise<void> {
|
||||
await this._databasesTable.updateProperties({
|
||||
data: this._databaseFiles,
|
||||
height: getTableHeight(this._databaseFiles.length, DefaultMinTableRowCount)
|
||||
});
|
||||
this.onFormFieldChange();
|
||||
}
|
||||
|
||||
protected override get helpUrl(): string {
|
||||
return AttachDatabaseDocUrl;
|
||||
}
|
||||
|
||||
protected override async validateInput(): Promise<string[]> {
|
||||
let errors = [];
|
||||
if (this._databasesToAttach.length === 0) {
|
||||
errors.push(loc.NoDatabaseFilesError);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
protected override async saveChanges(contextId: string, object: ObjectManagement.SqlObject): Promise<void> {
|
||||
await this.objectManagementService.attachDatabases(this.options.connectionUri, this._databasesToAttach, false);
|
||||
}
|
||||
|
||||
protected override async generateScript(): Promise<string> {
|
||||
return await this.objectManagementService.attachDatabases(this.options.connectionUri, this._databasesToAttach, true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user