mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-12 19:18:32 -05:00
Enabling Files tab to the database properties (#24138)
* initial changes for loadin dsc table with real values from smo * Displaying diff columns for DSC for diff sql server * checkbox maiants the selection * elevate option fails to load correct value when set to when_supported option * all working till maxdop, todo pause option, save * commented MAXDOP changes, as it is causing issues * primary,sec,checkbox working as expected, TODO:MaxDop etc options,saving,tests * Undo MAXDOP commented code * refactored with service data * column header width adjustments * Maxdop and pause resume options completed, apply button is failing now * Removed option names from loc and using Id instead as names may change in future like in doc * Apply button fixed * refactored to reduce table reload * Ledger digest completed * refactor done: maxdop secondary shows wrong data from pause_resume * refactor more: all working but table focus disturbs on update table * adds conditions for unsupported dsc to <2016 server * maxdop secondary checkbox fix * rows still loses focus after value change due to update table row data * Fixed updating secondary dropdown value * reusing the private method and removed the duplicated codes * initial commit - fullText and owner need revision * Enter key in input type allows the change to update the table data, reduces the live update issues * Setting focus to the current row * loading data, need stylings-increase col length, etc * using the existed setTableData method * Adding new file dialog * creating addFile, but not displaying in table, issue with appendData * Adding row to the table, options are getting from STS * all working except InPercent value * code review comment updates * Input type checkbox update table additional validation * all except path * fixing the input type focus and reverting the enterKeyPress logic * browse path is created, need stylings,refactor,filestream selection and add * fixing the flickering issue with data refresh * new file options toggle and grid display string updates * moving code inline and using actual component * cleanup * Add file saving is done, except one styling issue with autogrowth section * add,remove working, need to edit file * add, edit, remove - all working, need css fixes and -1 fix * addressing code review comments * adding local changes adn fixing for edit file * adjusting css * addressing code review comment for using loc var instead of duplicated line of code to get the rowinfo * all fixed, need testing and refactor * vBump STS and fixing required field causing the apply button not enable for other options on main branch * fixing autogrowth radio buttons change updates * all working except some css * disabled size for filestream * fixing filegroups and filetypes scnearios, added filename validation for newfile, todo:editingNew file * added max and min values to the inputs * editing filename validation completed, all done exccept CSS * all fixed except scroll bar * edit db file header, filename enable issue fix * PR comment supporting updates for STS * min updates * modfying addButtonsForTable method and reusing it for edit button * code review comment updates * Dialogbase addbuttons to table refactored * more typo fixes * removing fulltext index prop * service fix * using path.join instead of hardcoded separators * final commit changes
This commit is contained in:
committed by
GitHub
parent
9557e77982
commit
c4b1765745
@@ -36,6 +36,7 @@ export const DatabaseGeneralPropertiesDocUrl = 'https://learn.microsoft.com/sql/
|
||||
export const DatabaseOptionsPropertiesDocUrl = 'https://learn.microsoft.com/sql/relational-databases/databases/database-properties-options-page'
|
||||
export const DropDatabaseDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/drop-database-transact-sql';
|
||||
export const DatabaseScopedConfigurationPropertiesDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/alter-database-scoped-configuration-transact-sql'
|
||||
export const DatabaseFilesPropertiesDocUrl = 'https://learn.microsoft.com/sql/relational-databases/databases/database-properties-files-page'
|
||||
|
||||
export const enum TelemetryActions {
|
||||
CreateObject = 'CreateObject',
|
||||
|
||||
@@ -455,6 +455,8 @@ export interface Database extends ObjectManagement.SqlObject {
|
||||
encryptionEnabled: boolean;
|
||||
restrictAccess?: string;
|
||||
databaseScopedConfigurations: DatabaseScopedConfigurationsInfo[];
|
||||
isFilesTabSupported?: boolean;
|
||||
files?: DatabaseFile[];
|
||||
}
|
||||
|
||||
export interface DatabaseViewInfo extends ObjectManagement.ObjectViewInfo<Database> {
|
||||
@@ -466,7 +468,6 @@ export interface DatabaseViewInfo extends ObjectManagement.ObjectViewInfo<Databa
|
||||
compatibilityLevels?: OptionsCollection;
|
||||
containmentTypes?: OptionsCollection;
|
||||
recoveryModels?: OptionsCollection;
|
||||
files?: DatabaseFile[];
|
||||
azureBackupRedundancyLevels?: string[];
|
||||
azureServiceLevelObjectives?: AzureEditionDetails[];
|
||||
azureEditions?: string[];
|
||||
@@ -476,6 +477,9 @@ export interface DatabaseViewInfo extends ObjectManagement.ObjectViewInfo<Databa
|
||||
dscOnOffOptions?: string[];
|
||||
dscElevateOptions?: string[];
|
||||
dscEnableDisableOptions?: string[];
|
||||
rowDataFileGroupsOptions?: string[];
|
||||
fileStreamFileGroupsOptions?: string[];
|
||||
fileTypesOptions?: string[];
|
||||
}
|
||||
|
||||
export interface DatabaseScopedConfigurationsInfo {
|
||||
@@ -545,9 +549,22 @@ export interface NumericServerProperty {
|
||||
export interface ServerViewInfo extends ObjectManagement.ObjectViewInfo<Server> {
|
||||
}
|
||||
|
||||
export const enum FileGrowthType {
|
||||
KB = 0,
|
||||
Percent = 1,
|
||||
None = 99
|
||||
}
|
||||
|
||||
export interface DatabaseFile {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string;
|
||||
path: string;
|
||||
fileGroup: string;
|
||||
fileNameWithExtension: string;
|
||||
sizeInMb: number;
|
||||
isAutoGrowthEnabled: boolean;
|
||||
autoFileGrowth: number;
|
||||
autoFileGrowthType: FileGrowthType;
|
||||
maxSizeLimitInMb: number
|
||||
}
|
||||
|
||||
@@ -155,6 +155,7 @@ export const NameText = localize('objectManagement.nameLabel', "Name");
|
||||
export const GeneralSectionHeader = localize('objectManagement.generalSectionHeader', "General");
|
||||
export const AdvancedSectionHeader = localize('objectManagement.advancedSectionHeader', "Advanced");
|
||||
export const OptionsSectionHeader = localize('objectManagement.optionsSectionHeader', "Options");
|
||||
export const FilesSectionHeader = localize('objectManagement.optionsSectionHeader', "Files");
|
||||
export const PasswordText = localize('objectManagement.passwordLabel', "Password");
|
||||
export const ConfirmPasswordText = localize('objectManagement.confirmPasswordLabel', "Confirm password");
|
||||
export const EnabledText = localize('objectManagement.enabledLabel', "Enabled");
|
||||
@@ -323,6 +324,46 @@ export const DatabaseScopedOptionsColumnHeader = localize('objectManagement.data
|
||||
export const ValueForPrimaryColumnHeader = localize('objectManagement.databaseProperties.valueForPrimaryColumnHeader', "Value for Primary");
|
||||
export const ValueForSecondaryColumnHeader = localize('objectManagement.databaseProperties.valueForSecondaryColumnHeader', "Value for Secondary");
|
||||
export const SetSecondaryText = localize('objectManagement.databaseProperties.setSecondaryText', "Set Secondary same as Primary");
|
||||
export const DatabaseNameText = localize('objectManagement.databaseProperties.databaseNameLabel', "Database Name");
|
||||
export const UseFullTextIndexingText = localize('objectManagement.databaseProperties.useFullTextIndexingText', "Use full-text indexing");
|
||||
export const LogicalNameText = localize('objectManagement.databaseProperties.logicalNameText', "Logical Name");
|
||||
export const FileTypeText = localize('objectManagement.databaseProperties.fileTypeText', "File Type");
|
||||
export const FilegroupText = localize('objectManagement.databaseProperties.filegroupText', "Filegroup");
|
||||
export const AutogrowthMaxsizeText = localize('objectManagement.databaseProperties.autogrowthMaxsizeText', "Autogrowth / Maxsize");
|
||||
export const PathText = localize('objectManagement.databaseProperties.pathText', "Path");
|
||||
export const FileNameText = localize('objectManagement.databaseProperties.fileNameText', "File Name");
|
||||
export const DatabaseFilesText = localize('objectManagement.databaseProperties.databaseFilesText', "Database files");
|
||||
export const AddDatabaseFilesText = localize('objectManagement.databaseProperties.addDatabaseFilesText', "Add Database file");
|
||||
export const EditDatabaseFilesText = (fileName: string) => localize('objectManagement.databaseProperties.editDatabaseFilesText', "Edit Database file - {0}", fileName);
|
||||
export const AddButton = localize('objectManagement.databaseProperties.addButton', "Add");
|
||||
export const EditButton = localize('objectManagement.databaseProperties.editButton', "Edit");
|
||||
export const RemoveButton = localize('objectManagement.databaseProperties.removeButton', "Remove");
|
||||
export const SizeInMbText = localize('objectManagement.databaseProperties.size', "Size (MB)");
|
||||
export const EnableAutogrowthText = localize('objectManagement.databaseProperties.enableAutogrowthText', "Enable Autogrowth");
|
||||
export const FileGrowthText = localize('objectManagement.databaseProperties.fileGrowthText', "File Growth");
|
||||
export const MaximumFileSizeText = localize('objectManagement.databaseProperties.maximumFileSizeText', "Maximum File Size");
|
||||
export const InPercentAutogrowthText = localize('objectManagement.databaseProperties.inPercentAutogrowthText', "In Percent");
|
||||
export const InMegabytesAutogrowthText = localize('objectManagement.databaseProperties.inMegabytesAutogrowthText', "In Megabytes");
|
||||
export const LimitedToMBFileSizeText = localize('objectManagement.databaseProperties.limitedToMBFileSizeText', "Limited to (MB)");
|
||||
export const UnlimitedFileSizeText = localize('objectManagement.databaseProperties.unlimitedFileSizeText', "Unlimited");
|
||||
export const NoneText = localize('objectManagement.databaseProperties.noneText', "None");
|
||||
export function AutoGrowthValueStringGenerator(isFileGrowthSupported: boolean, fileGrowth: string, isFleGrowthInPercent: boolean, maxFileSize: number): string {
|
||||
const maxSizelimitation = maxFileSize === -1
|
||||
? localize('objectManagement.databaseProperties.autoGrowthValueConversion.unlimited', "Unlimited")
|
||||
: localize('objectManagement.databaseProperties.autoGrowthValueConversion.limitation', "Limited to {0} MB", maxFileSize);
|
||||
return isFileGrowthSupported ? localize('objectManagement.databaseProperties.autoGrowthValueConversion', "By {0} {1}, {2}", fileGrowth, isFleGrowthInPercent ? "Percent" : "MB", maxSizelimitation)
|
||||
: localize('objectManagement.databaseProperties.autoGrowthValueConversion', "{0}", maxSizelimitation);
|
||||
}
|
||||
export const FileGroupForLogTypeText = localize('objectManagement.databaseProperties.fileGroupNotApplicableText', "Not Applicable");
|
||||
export const FileGroupForFilestreamTypeText = localize('objectManagement.databaseProperties.fileGroupNotApplicableText', "No Applicable Filegroup");
|
||||
export const DuplicateLogicalNameError = (name: string) => localize('objectManagement.databaseProperties.fileGroupNotApplicableText', "DataFile '{0}' could not be added to the collection, because it already exists.", name);
|
||||
export const FileNameExistsError = (name: string) => localize('objectManagement.databaseProperties.fileNameExistsError', "The Logical file name '{0}' is already in use. Choose a different name.", name);
|
||||
export const FileAlreadyExistsError = (fullFilePath: string) => localize('objectManagement.databaseProperties.fileNameExistsError', "Cannot create file '{0}' because it already exists.", fullFilePath);
|
||||
export const FileSizeLimitError = localize('objectManagement.databaseProperties.fileSizeLimitError', "Maximum file size cannot be less than size");
|
||||
export const FilegrowthLimitError = localize('objectManagement.databaseProperties.filegrowthLimitError', "Filegrowth cannot be greater than the Maximum file size for a file");
|
||||
export const RowsDataFileType = localize('objectManagement.databaseProperties.rowsDataFileType', "ROWS Data");
|
||||
export const LogFiletype = localize('objectManagement.databaseProperties.logfiletype', "LOG");
|
||||
export const FilestreamFileType = localize('objectManagement.databaseProperties.filestreamFileType', "FILESTREAM Data");
|
||||
|
||||
// Util functions
|
||||
export function getNodeTypeDisplayName(type: string, inTitle: boolean = false): string {
|
||||
|
||||
@@ -470,12 +470,16 @@ export class TestObjectManagementService implements IObjectManagementService {
|
||||
collationNames: { defaultValueIndex: 0, options: ['Latin1_General_100_CI_AS_KS_WS', 'Latin1_General_100_CI_AS_KS_WS_SC'] },
|
||||
compatibilityLevels: { defaultValueIndex: 0, options: ['SQL Server 2008', 'SQL Server 2012', 'SQL Server 2014', 'SQL Server 2016', 'SQL Server 2017', 'SQL Server 2019'] },
|
||||
containmentTypes: { defaultValueIndex: 0, options: ['NONE', 'PARTIAL'] },
|
||||
loginNames: { defaultValueIndex: 0, options: ['user1', 'user2', 'user3'] },
|
||||
restrictAccessOptions: ['MULTI_USER', 'RESTRICTED_USER', 'SINGLE_USER'],
|
||||
recoveryModels: { defaultValueIndex: 0, options: ['FULL', 'SIMPLE', 'BULK_LOGGED'] },
|
||||
pageVerifyOptions: ['CHECKSUM', 'NONE', 'TORN_PAGE_DETECTION'],
|
||||
dscElevateOptions: ['OFF', 'WHEN_SUPPORTED', 'FAIL_UNSUPPORTED'],
|
||||
dscEnableDisableOptions: ['ENABLED', 'DISABLED'],
|
||||
dscOnOffOptions: ['ON', 'OFF'],
|
||||
rowDataFileGroupsOptions: ['PRIMARY', 'RowDataGroup1', 'RowDataGroup2'],
|
||||
fileStreamFileGroupsOptions: ['PRIMARY', 'FileStreamGroup1', 'FileStreamGroup2'],
|
||||
fileTypesOptions: ['ROWS', 'LOG', 'FILESTREAM'],
|
||||
objectInfo: {
|
||||
name: 'Database Properties1',
|
||||
collationName: 'Latin1_General_100_CI_AS_KS_WS',
|
||||
@@ -510,6 +514,11 @@ export class TestObjectManagementService implements IObjectManagementService {
|
||||
{ name: 'batch_mode_memory_grant_feedback', valueForPrimary: 'OFF', valueForSecondary: 'OFF' },
|
||||
{ name: 'batch_mode_adaptive_joins', valueForPrimary: 'OFF', valueForSecondary: 'ON' },
|
||||
{ name: 'tsql_scalar_udf_inlining', valueForPrimary: 'ON', valueForSecondary: 'ON' }
|
||||
],
|
||||
isFilesTabSupported: true,
|
||||
files: [
|
||||
{ id: 1, name: 'databasefile1', type: 'ROWS Data', path: 'C:\\Temp\\', fileGroup: 'PRIMARY', fileNameWithExtension: 'databasefile1.mdf', sizeInMb: 62, isAutoGrowthEnabled: true, autoFileGrowth: 64, autoFileGrowthType: 0, maxSizeLimitInMb: -1 },
|
||||
{ id: 2, name: 'databasefile1_Log', type: 'Log', path: 'C:\\Temp\\', fileGroup: 'Not Applicable', fileNameWithExtension: 'databasefile1_log.ldf', sizeInMb: 62, isAutoGrowthEnabled: true, autoFileGrowth: 64, autoFileGrowthType: 1, maxSizeLimitInMb: -1 },
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,15 @@
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase';
|
||||
import { DefaultInputWidth, DefaultTableWidth, getTableHeight } from '../../ui/dialogBase';
|
||||
import { DefaultInputWidth, DefaultTableWidth, DefaultMinTableRowCount, DefaultMaxTableRowCount, getTableHeight, DialogButton } from '../../ui/dialogBase';
|
||||
import { IObjectManagementService } from 'mssql';
|
||||
import * as localizedConstants from '../localizedConstants';
|
||||
import { CreateDatabaseDocUrl, DatabaseGeneralPropertiesDocUrl, DatabaseOptionsPropertiesDocUrl, DatabaseScopedConfigurationPropertiesDocUrl } from '../constants';
|
||||
import { Database, DatabaseScopedConfigurationsInfo, DatabaseViewInfo } from '../interfaces';
|
||||
import { CreateDatabaseDocUrl, DatabaseGeneralPropertiesDocUrl, DatabaseFilesPropertiesDocUrl, DatabaseOptionsPropertiesDocUrl, DatabaseScopedConfigurationPropertiesDocUrl } from '../constants';
|
||||
import { Database, DatabaseFile, DatabaseScopedConfigurationsInfo, DatabaseViewInfo, FileGrowthType } from '../interfaces';
|
||||
import { convertNumToTwoDecimalStringInMB } from '../utils';
|
||||
import { isUndefinedOrNull } from '../../types';
|
||||
import { deepClone } from '../../util/objects';
|
||||
import { DatabaseFileDialog } from './databaseFileDialog';
|
||||
|
||||
const MAXDOP_Max_Limit = 32767;
|
||||
const PAUSED_RESUMABLE_INDEX_Max_Limit = 71582;
|
||||
@@ -21,6 +22,7 @@ const DscTableRowLength = 15;
|
||||
export class DatabaseDialog extends ObjectManagementDialogBase<Database, DatabaseViewInfo> {
|
||||
// Database Properties tabs
|
||||
private generalTab: azdata.Tab;
|
||||
private filesTab: azdata.Tab;
|
||||
private optionsTab: azdata.Tab;
|
||||
private dscTab: azdata.Tab;
|
||||
private optionsTabSectionsContainer: azdata.Component[] = [];
|
||||
@@ -43,6 +45,9 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
|
||||
private memoryAllocatedInput: azdata.InputBoxComponent;
|
||||
private memoryUsedInput: azdata.InputBoxComponent;
|
||||
private collationInput: azdata.InputBoxComponent;
|
||||
// Files Tab
|
||||
private readonly filesTabId: string = 'filesDatabaseId';
|
||||
private databaseFilesTable: azdata.TableComponent;
|
||||
// Options Tab
|
||||
private readonly optionsTabId: string = 'optionsDatabaseId';
|
||||
private autoCreateIncrementalStatisticsInput: azdata.CheckBoxComponent;
|
||||
@@ -91,6 +96,9 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
|
||||
case this.generalTabId:
|
||||
helpUrl = DatabaseGeneralPropertiesDocUrl;
|
||||
break;
|
||||
case this.filesTabId:
|
||||
helpUrl = DatabaseFilesPropertiesDocUrl;
|
||||
break;
|
||||
case this.optionsTabId:
|
||||
helpUrl = DatabaseOptionsPropertiesDocUrl;
|
||||
break;
|
||||
@@ -113,11 +121,11 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
|
||||
}
|
||||
this.formContainer.addItems(components);
|
||||
} else {
|
||||
// Initilaize general Tab sections
|
||||
// Initialize general Tab sections
|
||||
this.initializeBackupSection();
|
||||
this.initializeDatabaseSection();
|
||||
|
||||
//Initilaize options Tab sections
|
||||
//Initialize options Tab sections
|
||||
this.initializeOptionsGeneralSection();
|
||||
this.initializeAutomaticSection();
|
||||
if (!isUndefinedOrNull(this.objectInfo.isLedgerDatabase)) {
|
||||
@@ -130,7 +138,7 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
|
||||
|
||||
|
||||
const tabs: azdata.Tab[] = [];
|
||||
// Initilaize general Tab
|
||||
// Initialize general Tab
|
||||
this.generalTab = {
|
||||
title: localizedConstants.GeneralSectionHeader,
|
||||
id: this.generalTabId,
|
||||
@@ -141,7 +149,20 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
|
||||
};
|
||||
tabs.push(this.generalTab);
|
||||
|
||||
// Initilaize Options Tab
|
||||
// Initialize Files Tab
|
||||
// Files tab is only enabled for SQL Server properties view
|
||||
if (!isUndefinedOrNull(this.objectInfo.isFilesTabSupported)) {
|
||||
const filesGeneralSection = this.initializeFilesGeneralSection();
|
||||
const databaseFilesSection = this.initializeDatabaseFilesSection();
|
||||
this.filesTab = {
|
||||
title: localizedConstants.FilesSectionHeader,
|
||||
id: this.filesTabId,
|
||||
content: this.createGroup('', [filesGeneralSection, databaseFilesSection], false)
|
||||
};
|
||||
tabs.push(this.filesTab);
|
||||
}
|
||||
|
||||
// Initialize Options Tab
|
||||
this.optionsTab = {
|
||||
title: localizedConstants.OptionsSectionHeader,
|
||||
id: this.optionsTabId,
|
||||
@@ -149,7 +170,7 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
|
||||
};
|
||||
tabs.push(this.optionsTab);
|
||||
|
||||
// Initilaize DSC Tab section
|
||||
// Initialize DSC Tab section
|
||||
if (!isUndefinedOrNull(this.objectInfo.databaseScopedConfigurations)) {
|
||||
await this.initializeDatabaseScopedConfigurationSection();
|
||||
this.dscTabSectionsContainer.push(await this.initializeDscValueDropdownTypeSection())
|
||||
@@ -162,7 +183,7 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
|
||||
tabs.push(this.dscTab);
|
||||
}
|
||||
|
||||
// Initilaize tab group with tabbed panel
|
||||
// Initialize tab group with tabbed panel
|
||||
const propertiesTabGroup = { title: '', tabs: tabs };
|
||||
const propertiesTabbedPannel = this.modelView.modelBuilder.tabbedPanel()
|
||||
.withTabs([propertiesTabGroup])
|
||||
@@ -396,6 +417,216 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Database Properties - Files Tab
|
||||
private initializeFilesGeneralSection(): azdata.GroupContainer {
|
||||
let containers: azdata.Component[] = [];
|
||||
// Database name
|
||||
this.nameInput = this.createInputBox(async () => { }, {
|
||||
ariaLabel: localizedConstants.DatabaseNameText,
|
||||
inputType: 'text',
|
||||
enabled: this.options.isNewObject,
|
||||
value: this.objectInfo.name
|
||||
});
|
||||
containers.push(this.createLabelInputContainer(localizedConstants.DatabaseNameText, this.nameInput));
|
||||
|
||||
// Owner
|
||||
let loginNames = this.viewInfo.loginNames?.options;
|
||||
|
||||
if (loginNames?.length > 0) {
|
||||
// Removing <default> login name from the list and adding current owner if not exists
|
||||
if (!this.viewInfo.loginNames?.options.find(owner => owner === this.objectInfo.owner)) {
|
||||
loginNames[0] = this.objectInfo.owner;
|
||||
} else {
|
||||
loginNames.splice(0, 1);
|
||||
}
|
||||
let ownerDropbox = this.createDropdown(localizedConstants.OwnerText, async () => {
|
||||
this.objectInfo.owner = ownerDropbox.value as string;
|
||||
}, loginNames, this.objectInfo.owner);
|
||||
containers.push(this.createLabelInputContainer(localizedConstants.OwnerText, ownerDropbox));
|
||||
}
|
||||
return this.createGroup('', containers, false);
|
||||
}
|
||||
|
||||
private initializeDatabaseFilesSection(): azdata.GroupContainer {
|
||||
this.databaseFilesTable = this.modelView.modelBuilder.table().withProps({
|
||||
columns: [{
|
||||
type: azdata.ColumnType.text,
|
||||
value: localizedConstants.LogicalNameText
|
||||
}, {
|
||||
type: azdata.ColumnType.text,
|
||||
value: localizedConstants.FileTypeText
|
||||
}, {
|
||||
type: azdata.ColumnType.text,
|
||||
value: localizedConstants.FilegroupText
|
||||
}, {
|
||||
type: azdata.ColumnType.text,
|
||||
value: localizedConstants.SizeInMbText
|
||||
}, {
|
||||
type: azdata.ColumnType.text,
|
||||
value: localizedConstants.AutogrowthMaxsizeText
|
||||
}, {
|
||||
type: azdata.ColumnType.text,
|
||||
value: localizedConstants.PathText
|
||||
}, {
|
||||
type: azdata.ColumnType.text,
|
||||
value: localizedConstants.FileNameText
|
||||
}],
|
||||
data: this.objectInfo.files?.map(file => {
|
||||
return this.convertToDataView(file);
|
||||
|
||||
}),
|
||||
height: getTableHeight(this.objectInfo.files?.length, DefaultMinTableRowCount, DefaultMaxTableRowCount),
|
||||
width: DefaultTableWidth,
|
||||
forceFitColumns: azdata.ColumnSizingMode.DataFit,
|
||||
CSSStyles: {
|
||||
'margin-left': '10px'
|
||||
}
|
||||
}).component();
|
||||
const addButtonComponent: DialogButton = {
|
||||
buttonAriaLabel: localizedConstants.AddButton,
|
||||
buttonHandler: (button) => this.onAddDatabaseFilesButtonClicked(button)
|
||||
};
|
||||
const removeButtonComponent: DialogButton = {
|
||||
buttonAriaLabel: localizedConstants.RemoveButton,
|
||||
buttonHandler: () => this.onRemoveDatabaseFilesButtonClicked()
|
||||
};
|
||||
const editbuttonComponent: DialogButton = {
|
||||
buttonAriaLabel: localizedConstants.EditButton,
|
||||
buttonHandler: (button) => this.onEditDatabaseFilesButtonClicked(button)
|
||||
};
|
||||
const databaseFilesButtonContainer = this.addButtonsForTable(this.databaseFilesTable, addButtonComponent, removeButtonComponent, editbuttonComponent);
|
||||
|
||||
return this.createGroup(localizedConstants.DatabaseFilesText, [this.databaseFilesTable, databaseFilesButtonContainer], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the file object to a data view object
|
||||
* @param file database file object
|
||||
* @returns data view object
|
||||
*/
|
||||
private convertToDataView(file: DatabaseFile): any[] {
|
||||
return [
|
||||
file.name,
|
||||
file.type,
|
||||
file.fileGroup,
|
||||
file.sizeInMb,
|
||||
file.isAutoGrowthEnabled ? localizedConstants.AutoGrowthValueStringGenerator(file.type !== localizedConstants.FilestreamFileType
|
||||
, file.autoFileGrowth.toString()
|
||||
, file.autoFileGrowthType === FileGrowthType.Percent
|
||||
, file.maxSizeLimitInMb) : localizedConstants.NoneText,
|
||||
file.path,
|
||||
file.fileNameWithExtension
|
||||
];
|
||||
}
|
||||
|
||||
private async onAddDatabaseFilesButtonClicked(button: azdata.ButtonComponent): Promise<void> {
|
||||
// Open file dialog to create file
|
||||
const result = await this.openDatabaseFileDialog(button);
|
||||
if (!isUndefinedOrNull(result)) {
|
||||
this.objectInfo.files?.push(result);
|
||||
var newData = this.objectInfo.files?.map(file => {
|
||||
return this.convertToDataView(file);
|
||||
});
|
||||
await this.setTableData(this.databaseFilesTable, newData, DefaultMaxTableRowCount)
|
||||
}
|
||||
}
|
||||
|
||||
private async onEditDatabaseFilesButtonClicked(button: azdata.ButtonComponent): Promise<void> {
|
||||
if (this.databaseFilesTable.selectedRows.length === 1) {
|
||||
const result = await this.openDatabaseFileDialog(button);
|
||||
if (!isUndefinedOrNull(result)) {
|
||||
this.objectInfo.files[this.databaseFilesTable.selectedRows[0]] = result;
|
||||
var newData = this.objectInfo.files?.map(file => {
|
||||
return this.convertToDataView(file);
|
||||
});
|
||||
await this.setTableData(this.databaseFilesTable, newData, DefaultMaxTableRowCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the selected database file from the table
|
||||
*/
|
||||
private async onRemoveDatabaseFilesButtonClicked(): Promise<void> {
|
||||
if (this.databaseFilesTable.selectedRows.length === 1) {
|
||||
this.objectInfo.files?.splice(this.databaseFilesTable.selectedRows[0], 1);
|
||||
var newData = this.objectInfo.files?.map(file => {
|
||||
return this.convertToDataView(file);
|
||||
});
|
||||
await this.setTableData(this.databaseFilesTable, newData, DefaultMaxTableRowCount)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the selected row to enable/disable the remove button
|
||||
* @returns true if the remove button should be enabled, false otherwise
|
||||
*/
|
||||
protected override get removeButtonEnabled(): boolean {
|
||||
let isEnabled = true;
|
||||
if (this.databaseFilesTable.selectedRows !== undefined) {
|
||||
const selectedRowId = this.objectInfo.files[this.databaseFilesTable.selectedRows[0]].id;
|
||||
// Cannot delete a Primary row data file, Id is always 1.
|
||||
if (this.databaseFilesTable.selectedRows.length === 1 && selectedRowId === 1) {
|
||||
isEnabled = false;
|
||||
}
|
||||
// Cannot remove a log file if there are no other log files, LogFiletype is always a Log file type
|
||||
else if (this.objectInfo.files[this.databaseFilesTable.selectedRows[0]].type === localizedConstants.LogFiletype) {
|
||||
isEnabled = false;
|
||||
this.objectInfo.files.forEach(file => {
|
||||
if (file.id !== selectedRowId && file.type === localizedConstants.LogFiletype) {
|
||||
isEnabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
private async openDatabaseFileDialog(button: azdata.ButtonComponent): Promise<DatabaseFile> {
|
||||
const defaultFileSizeInMb: number = 8
|
||||
const defaultFileGrowthInMb: number = 64
|
||||
const defaultFileGrowthInPercent: number = 10;
|
||||
const defaultMaxFileSizeLimitedToInMb: number = 100;
|
||||
const selectedFile = this.databaseFilesTable.selectedRows !== undefined ? this.objectInfo.files[this.databaseFilesTable?.selectedRows[0]] : undefined;
|
||||
if (!isUndefinedOrNull(selectedFile) && selectedFile.type === localizedConstants.FilestreamFileType) {
|
||||
selectedFile.autoFileGrowth = defaultFileGrowthInMb;
|
||||
}
|
||||
const isNewFile: boolean = button.ariaLabel === localizedConstants.AddButton;
|
||||
const isEditingNewFile: boolean = button.ariaLabel === localizedConstants.EditButton && selectedFile.id === undefined;
|
||||
const databaseFile: DatabaseFile = isNewFile ? {
|
||||
id: undefined,
|
||||
name: '',
|
||||
type: localizedConstants.RowsDataFileType,
|
||||
path: this.objectInfo.files[0].path,
|
||||
fileGroup: this.viewInfo.rowDataFileGroupsOptions[0],
|
||||
fileNameWithExtension: '',
|
||||
sizeInMb: defaultFileSizeInMb,
|
||||
isAutoGrowthEnabled: true,
|
||||
autoFileGrowth: defaultFileGrowthInMb,
|
||||
autoFileGrowthType: FileGrowthType.KB,
|
||||
maxSizeLimitInMb: defaultMaxFileSizeLimitedToInMb
|
||||
} : selectedFile;
|
||||
|
||||
const dialog = new DatabaseFileDialog({
|
||||
title: (isNewFile || isEditingNewFile) ? localizedConstants.AddDatabaseFilesText : localizedConstants.EditDatabaseFilesText(databaseFile.name),
|
||||
viewInfo: this.viewInfo,
|
||||
files: this.objectInfo.files,
|
||||
isNewFile: isNewFile,
|
||||
isEditingNewFile: isEditingNewFile,
|
||||
databaseFile: databaseFile,
|
||||
defaultFileConstants: {
|
||||
defaultFileSizeInMb: defaultFileSizeInMb,
|
||||
defaultFileGrowthInMb: defaultFileGrowthInMb,
|
||||
defaultFileGrowthInPercent: defaultFileGrowthInPercent,
|
||||
defaultMaxFileSizeLimitedToInMb: defaultMaxFileSizeLimitedToInMb
|
||||
}
|
||||
});
|
||||
await dialog.open();
|
||||
return await dialog.waitForClose();
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Database Properties - Options Tab
|
||||
private initializeOptionsGeneralSection(): void {
|
||||
let containers: azdata.Component[] = [];
|
||||
|
||||
384
extensions/mssql/src/objectManagement/ui/databaseFileDialog.ts
Normal file
384
extensions/mssql/src/objectManagement/ui/databaseFileDialog.ts
Normal file
@@ -0,0 +1,384 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { DefaultInputWidth, DialogBase } from '../../ui/dialogBase';
|
||||
import * as localizedConstants from '../localizedConstants';
|
||||
import { DatabaseFile, DatabaseViewInfo, FileGrowthType } from '../interfaces';
|
||||
import { isUndefinedOrNull } from '../../types';
|
||||
import { deepClone } from '../../util/objects';
|
||||
|
||||
export interface NewDatabaseFileDialogOptions {
|
||||
title: string;
|
||||
viewInfo: DatabaseViewInfo;
|
||||
files: DatabaseFile[];
|
||||
isNewFile: boolean;
|
||||
isEditingNewFile: boolean;
|
||||
databaseFile: DatabaseFile;
|
||||
defaultFileConstants: {
|
||||
defaultFileSizeInMb: number,
|
||||
defaultFileGrowthInPercent: number,
|
||||
defaultFileGrowthInMb: number,
|
||||
defaultMaxFileSizeLimitedToInMb: number
|
||||
};
|
||||
}
|
||||
|
||||
const fileSizeInputMaxValueInMbForDataType = 16776192; // Row type supports up to 16 TB (SSMS allows =~ 15.99TB)
|
||||
const fileSizeInputMaxValueInMbForLogType = 2 * 1024 * 1024; // Row type supports up to 2 TB
|
||||
const fileSizeInputMaxValueInPercent = 100; // SSMS allows more than 100, but we are limiting to 100 in ADS
|
||||
|
||||
export class DatabaseFileDialog extends DialogBase<DatabaseFile> {
|
||||
private result: DatabaseFile;
|
||||
private fileSizeInput: azdata.InputBoxComponent;
|
||||
private fileNameWithExtension: azdata.InputBoxComponent;
|
||||
private fileGroupDropdown: azdata.DropDownComponent;
|
||||
private AutogrowthGroup: azdata.GroupContainer;
|
||||
private fileGrowthGroup: azdata.GroupContainer;
|
||||
private maxSizeGroup: azdata.GroupContainer;
|
||||
private pathContainer: azdata.FlexContainer;
|
||||
private enableAutoGrowthCheckbox: azdata.CheckBoxComponent;
|
||||
private inPercentAutogrowth: azdata.RadioButtonComponent;
|
||||
private inMegabytesAutogrowth: azdata.RadioButtonComponent;
|
||||
private autoFilegrowthInput: azdata.InputBoxComponent;
|
||||
private autogrowthInPercentValue: number;
|
||||
private autogrowthInMegabytesValue: number;
|
||||
private limitedToMbFileSize: azdata.RadioButtonComponent;
|
||||
private unlimitedFileSize: azdata.RadioButtonComponent;
|
||||
private limitedToMbFileSizeInput: azdata.InputBoxComponent;
|
||||
private fileSizeValue: number;
|
||||
protected filePathButton: azdata.ButtonComponent;
|
||||
protected filePathTextBox: azdata.InputBoxComponent;
|
||||
private originalName: string;
|
||||
private originalFileName: string;
|
||||
private isEditingFile: boolean;
|
||||
|
||||
constructor(private readonly options: NewDatabaseFileDialogOptions) {
|
||||
super(options.title, 'DatabaseFileDialog');
|
||||
}
|
||||
|
||||
protected override async initialize(): Promise<void> {
|
||||
this.dialogObject.okButton.enabled = false;
|
||||
this.autogrowthInPercentValue = this.options.defaultFileConstants.defaultFileGrowthInPercent;
|
||||
this.autogrowthInMegabytesValue = this.options.defaultFileConstants.defaultFileGrowthInMb;
|
||||
this.result = deepClone(this.options.databaseFile);
|
||||
this.originalName = this.options.databaseFile.name;
|
||||
this.originalFileName = this.options.databaseFile.fileNameWithExtension;
|
||||
this.isEditingFile = this.options.isNewFile || this.options.isEditingNewFile;
|
||||
await this.initializeAddDatabaseFileDialog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the file properties and returns an array of error messages
|
||||
* @returns array of error messages if validation fails or empty array if validation succeeds
|
||||
*/
|
||||
protected override async validateInput(): Promise<string[]> {
|
||||
const errors = await super.validateInput();
|
||||
// Name validations
|
||||
if (this.result.name !== this.originalName) {
|
||||
// If adding a new file, can check if no exisiting file should have the same name
|
||||
// If editing a new file, modified name should not be matched in the collection, if length != 0 means some other file has the name already
|
||||
if ((this.options.isNewFile && !!this.options.files.find(file => { return file.name === this.result.name.trim() })) ||
|
||||
(this.options.isEditingNewFile && this.options.files.filter(file => { return file.name === this.result.name.trim() }).length !== 0)) {
|
||||
errors.push(localizedConstants.DuplicateLogicalNameError(this.result.name.trim()));
|
||||
}
|
||||
// If editing existing file, current name should not be same as any other existing file
|
||||
if (!this.options.isNewFile && !this.options.isEditingNewFile && !!this.options.files.find(file => { return this.result.id !== file.id && file.name === this.result.name.trim() })) {
|
||||
errors.push(localizedConstants.FileNameExistsError(this.result.name.trim()));
|
||||
}
|
||||
// If new file, verify if the file name with extension already exists
|
||||
if (this.options.isNewFile && !!this.options.files.find(file => { return (path.join(file.path, file.fileNameWithExtension) === path.join(this.result.path, this.result.fileNameWithExtension)) })) {
|
||||
errors.push(localizedConstants.FileAlreadyExistsError(path.join(this.result.path, this.result.fileNameWithExtension)));
|
||||
}
|
||||
}
|
||||
|
||||
// If editing a new file and the file name with extension is modified, verify if the file name with extension already exists
|
||||
if (this.options.isEditingNewFile && this.result.fileNameWithExtension !== this.originalFileName) {
|
||||
if (this.options.files.filter(file => { return (path.join(file.path, file.fileNameWithExtension)) === (path.join(this.result.path, this.result.fileNameWithExtension)) }).length !== 0) {
|
||||
errors.push(localizedConstants.FileAlreadyExistsError(path.join(this.result.path, this.result.fileNameWithExtension)));
|
||||
}
|
||||
}
|
||||
|
||||
// When maxsize is limited and size should not be greater than maxSize allowed
|
||||
if (this.result.maxSizeLimitInMb !== -1 && this.result.maxSizeLimitInMb < this.result.sizeInMb) {
|
||||
errors.push(localizedConstants.FileSizeLimitError);
|
||||
}
|
||||
// When maxsize is limited and fileGrowth should not be greater than maxSize allowed
|
||||
if (this.result.maxSizeLimitInMb !== -1 && this.result.autoFileGrowthType !== FileGrowthType.Percent
|
||||
&& this.result.maxSizeLimitInMb < this.result.autoFileGrowth) {
|
||||
errors.push(localizedConstants.FilegrowthLimitError);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
private async initializeAddDatabaseFileDialog(): Promise<void> {
|
||||
let containers: azdata.Component[] = [];
|
||||
// Logical Name of the file
|
||||
const logicalname = this.createInputBox(async (newValue) => {
|
||||
if (newValue.trim() !== '') {
|
||||
this.result.name = newValue.trim();
|
||||
this.fileNameWithExtension.value = this.generateFileNameWithExtension();
|
||||
}
|
||||
}, {
|
||||
ariaLabel: localizedConstants.LogicalNameText,
|
||||
inputType: 'text',
|
||||
enabled: true,
|
||||
value: this.options.databaseFile.name,
|
||||
required: true
|
||||
});
|
||||
const filenameContainer = this.createLabelInputContainer(localizedConstants.LogicalNameText, logicalname);
|
||||
containers.push(filenameContainer);
|
||||
|
||||
// File Type
|
||||
const fileType = this.createDropdown(localizedConstants.FileTypeText, async (newValue) => {
|
||||
await this.updateOptionsForSelectedFileType(newValue);
|
||||
this.result.type = newValue;
|
||||
this.fileNameWithExtension.value = this.generateFileNameWithExtension();
|
||||
}, this.options.viewInfo.fileTypesOptions, this.result.type, this.isEditingFile, DefaultInputWidth);
|
||||
const fileTypeContainer = this.createLabelInputContainer(localizedConstants.FileTypeText, fileType);
|
||||
containers.push(fileTypeContainer);
|
||||
|
||||
// Filegroup
|
||||
this.fileGroupDropdown = this.createDropdown(localizedConstants.FilegroupText, async (newValue) => {
|
||||
this.result.fileGroup = newValue;
|
||||
}, this.options.viewInfo.rowDataFileGroupsOptions, this.options.databaseFile.fileGroup, this.isEditingFile, DefaultInputWidth);
|
||||
const sizeContainer = this.createLabelInputContainer(localizedConstants.FilegroupText, this.fileGroupDropdown);
|
||||
containers.push(sizeContainer);
|
||||
|
||||
// File Size in MB
|
||||
this.fileSizeInput = this.createInputBox(async (newValue) => {
|
||||
this.result.sizeInMb = Number(newValue);
|
||||
}, {
|
||||
ariaLabel: localizedConstants.SizeInMbText,
|
||||
inputType: 'number',
|
||||
enabled: this.options.databaseFile.type !== localizedConstants.FilestreamFileType,
|
||||
value: String(this.options.databaseFile.sizeInMb)
|
||||
});
|
||||
const fileSizeContainer = this.createLabelInputContainer(localizedConstants.SizeInMbText, this.fileSizeInput);
|
||||
containers.push(fileSizeContainer);
|
||||
|
||||
// Auto Growth and Max Size
|
||||
containers.push(await this.initializeAutogrowthSection());
|
||||
|
||||
// Path
|
||||
this.filePathTextBox = this.createInputBox(async (newValue) => {
|
||||
this.result.path = newValue;
|
||||
}, {
|
||||
ariaLabel: localizedConstants.PathText,
|
||||
inputType: 'text',
|
||||
enabled: this.isEditingFile,
|
||||
value: this.options.databaseFile.path,
|
||||
width: DefaultInputWidth - 30
|
||||
});
|
||||
this.filePathButton = this.createButton('...', '...', async () => { await this.createFileBrowser() }, this.options.isNewFile);
|
||||
this.filePathButton.width = 25;
|
||||
this.pathContainer = this.createLabelInputContainer(localizedConstants.PathText, this.filePathTextBox);
|
||||
this.pathContainer.addItems([this.filePathButton], { flex: '10 0 auto' });
|
||||
containers.push(this.pathContainer);
|
||||
|
||||
// File Name
|
||||
let fileNameEnabled = this.isEditingFile;
|
||||
if (fileNameEnabled) {
|
||||
fileNameEnabled = !(this.result.type === localizedConstants.FilestreamFileType);
|
||||
}
|
||||
this.fileNameWithExtension = this.createInputBox(async (newValue) => {
|
||||
this.result.fileNameWithExtension = newValue;
|
||||
}, {
|
||||
ariaLabel: localizedConstants.FileNameText,
|
||||
inputType: 'text',
|
||||
enabled: fileNameEnabled, // false for edit old file and for filestream type
|
||||
value: this.options.databaseFile.fileNameWithExtension,
|
||||
width: DefaultInputWidth
|
||||
});
|
||||
const fileNameWithExtensionContainer = this.createLabelInputContainer(localizedConstants.FileNameText, this.fileNameWithExtension);
|
||||
containers.push(fileNameWithExtensionContainer);
|
||||
|
||||
this.formContainer.addItems(containers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialized file growth and max file size sections
|
||||
* @returns a group container with 'auto file growth' options
|
||||
*/
|
||||
private async initializeAutogrowthSection(): Promise<azdata.GroupContainer> {
|
||||
// Autogrowth checkbox
|
||||
this.enableAutoGrowthCheckbox = this.createCheckbox(localizedConstants.EnableAutogrowthText, async (checked) => {
|
||||
this.inPercentAutogrowth.enabled
|
||||
= this.inMegabytesAutogrowth.enabled
|
||||
= this.autoFilegrowthInput.enabled
|
||||
= this.limitedToMbFileSize.enabled
|
||||
= this.limitedToMbFileSizeInput.enabled
|
||||
= this.unlimitedFileSize.enabled
|
||||
= this.result.isAutoGrowthEnabled = checked;
|
||||
}, true, true);
|
||||
|
||||
// Autogrowth radio button and input section
|
||||
let radioGroupName = 'autogrowthRadioGroup';
|
||||
const isFileAutoGrowthInKB = this.options.databaseFile.autoFileGrowthType === FileGrowthType.KB;
|
||||
this.inPercentAutogrowth = this.createRadioButton(localizedConstants.InPercentAutogrowthText, radioGroupName, !isFileAutoGrowthInKB, async (checked) => { await this.handleAutogrowthTypeChange(checked); });
|
||||
this.inMegabytesAutogrowth = this.createRadioButton(localizedConstants.InMegabytesAutogrowthText, radioGroupName, isFileAutoGrowthInKB, async (checked) => { await this.handleAutogrowthTypeChange(checked); });
|
||||
this.autoFilegrowthInput = this.createInputBox(async (newValue) => {
|
||||
if (!isUndefinedOrNull(newValue) && newValue !== '') {
|
||||
if (this.inPercentAutogrowth.checked) {
|
||||
this.autogrowthInPercentValue = Number(newValue);
|
||||
} else {
|
||||
this.autogrowthInMegabytesValue = Number(newValue);
|
||||
}
|
||||
this.result.autoFileGrowth = this.inPercentAutogrowth.checked ? this.autogrowthInPercentValue : this.autogrowthInMegabytesValue;
|
||||
}
|
||||
}, {
|
||||
ariaLabel: localizedConstants.FileGrowthText,
|
||||
inputType: 'number',
|
||||
enabled: true,
|
||||
value: String(this.options.databaseFile.autoFileGrowth),
|
||||
width: DefaultInputWidth - 10,
|
||||
min: 1
|
||||
});
|
||||
const autogrowthContainer = this.createLabelInputContainer(localizedConstants.FileGrowthText, this.autoFilegrowthInput);
|
||||
this.fileGrowthGroup = this.createGroup('', [this.enableAutoGrowthCheckbox
|
||||
, autogrowthContainer, this.inPercentAutogrowth, this.inMegabytesAutogrowth], true);
|
||||
await this.fileGrowthGroup.updateCssStyles({ 'margin': '10px 0px -10px -10px' });
|
||||
|
||||
// Autogrowth radio button and input section
|
||||
radioGroupName = 'maxFileSizeRadioGroup';
|
||||
const isFileSizeLimited = this.options.isNewFile ? false : this.options.databaseFile.maxSizeLimitInMb !== -1;
|
||||
this.limitedToMbFileSize = this.createRadioButton(localizedConstants.LimitedToMBFileSizeText, radioGroupName, isFileSizeLimited, async (checked) => { await this.handleMaxFileSizeTypeChange(checked); });
|
||||
this.unlimitedFileSize = this.createRadioButton(localizedConstants.UnlimitedFileSizeText, radioGroupName, !isFileSizeLimited, async (checked) => { await this.handleMaxFileSizeTypeChange(checked); });
|
||||
this.limitedToMbFileSizeInput = this.createInputBox(async (newValue) => {
|
||||
this.fileSizeValue = Number(newValue);
|
||||
this.result.maxSizeLimitInMb = this.fileSizeValue;
|
||||
if (this.unlimitedFileSize.checked) {
|
||||
this.result.maxSizeLimitInMb = -1;
|
||||
}
|
||||
}, {
|
||||
ariaLabel: localizedConstants.MaximumFileSizeText,
|
||||
inputType: 'number',
|
||||
enabled: true,
|
||||
value: this.options.databaseFile.maxSizeLimitInMb === -1 ? String(this.options.defaultFileConstants.defaultMaxFileSizeLimitedToInMb) : String(this.options.databaseFile.maxSizeLimitInMb),
|
||||
width: DefaultInputWidth - 10,
|
||||
min: 1,
|
||||
max: this.options.databaseFile.type === localizedConstants.LogFiletype ? fileSizeInputMaxValueInMbForLogType : fileSizeInputMaxValueInMbForDataType
|
||||
});
|
||||
const fileSizeContainer = this.createLabelInputContainer(localizedConstants.MaximumFileSizeText, this.limitedToMbFileSizeInput);
|
||||
this.maxSizeGroup = this.createGroup('', [fileSizeContainer, this.limitedToMbFileSize, this.unlimitedFileSize], true);
|
||||
await this.maxSizeGroup.updateCssStyles({ 'margin': '10px 0px -10px -10px' });
|
||||
|
||||
this.AutogrowthGroup = this.createGroup(localizedConstants.AutogrowthMaxsizeText, [this.fileGrowthGroup, this.maxSizeGroup], false);
|
||||
return this.AutogrowthGroup;
|
||||
}
|
||||
|
||||
private async handleAutogrowthTypeChange(checked: boolean): Promise<void> {
|
||||
this.autoFilegrowthInput.value = this.options.isNewFile ? (this.inPercentAutogrowth.checked ? this.autogrowthInPercentValue?.toString() : this.autogrowthInMegabytesValue?.toString()) : this.options.databaseFile.autoFileGrowth?.toString();
|
||||
this.autoFilegrowthInput.max = this.inPercentAutogrowth.checked ? fileSizeInputMaxValueInPercent : (this.result.type === localizedConstants.LogFiletype ? fileSizeInputMaxValueInMbForLogType / 2 : fileSizeInputMaxValueInMbForDataType / 2);
|
||||
this.result.autoFileGrowthType = this.inPercentAutogrowth.checked ? FileGrowthType.Percent : FileGrowthType.KB;
|
||||
}
|
||||
|
||||
private async handleMaxFileSizeTypeChange(checked: boolean): Promise<void> {
|
||||
if (this.limitedToMbFileSize.checked) {
|
||||
this.limitedToMbFileSizeInput.enabled = true;
|
||||
this.result.maxSizeLimitInMb = this.fileSizeValue;
|
||||
} else if (this.unlimitedFileSize.checked) {
|
||||
this.limitedToMbFileSizeInput.enabled = false;
|
||||
this.result.maxSizeLimitInMb = -1; //Unlimited
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a file browser and sets the path to the filePath
|
||||
*/
|
||||
private async createFileBrowser(): Promise<void> {
|
||||
let fileUris = await vscode.window.showOpenDialog(
|
||||
{
|
||||
canSelectFiles: false,
|
||||
canSelectFolders: true,
|
||||
canSelectMany: false,
|
||||
defaultUri: vscode.Uri.file(this.options.databaseFile.path),
|
||||
openLabel: localizedConstants.SelectText
|
||||
}
|
||||
);
|
||||
|
||||
if (!fileUris || fileUris.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let fileUri = fileUris[0];
|
||||
this.filePathTextBox.value = fileUri.fsPath;
|
||||
this.result.path = fileUri.fsPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles fileGroup dropdown options and visibility of the autogrowth file group section based on the selected file type
|
||||
* @param selectedOption the selected option from the fileType dropdown
|
||||
*/
|
||||
private async updateOptionsForSelectedFileType(selectedOption: string): Promise<void> {
|
||||
// Row Data defaults
|
||||
let fileGroupDdOptions = this.options.viewInfo.rowDataFileGroupsOptions;
|
||||
let fileGroupDdValue = this.result.fileGroup;
|
||||
let visibility = 'visible';
|
||||
let maxSizeGroupMarginTop = '0px';
|
||||
let pathContainerMarginTop = '0px';
|
||||
let enableInputs = true;
|
||||
let fileSizeInputMaxValue = fileSizeInputMaxValueInMbForDataType;
|
||||
// Log
|
||||
if (selectedOption === localizedConstants.LogFiletype) {
|
||||
fileGroupDdOptions = [localizedConstants.FileGroupForLogTypeText];
|
||||
fileGroupDdValue = localizedConstants.FileGroupForLogTypeText;
|
||||
fileSizeInputMaxValue = fileSizeInputMaxValueInMbForLogType
|
||||
}
|
||||
// File Stream
|
||||
else if (selectedOption === localizedConstants.FilestreamFileType) {
|
||||
fileGroupDdOptions = this.options.viewInfo.fileStreamFileGroupsOptions;
|
||||
fileGroupDdValue = this.result.fileGroup;
|
||||
visibility = 'hidden';
|
||||
maxSizeGroupMarginTop = '-130px';
|
||||
pathContainerMarginTop = '-35px';
|
||||
enableInputs = false;
|
||||
this.fileNameWithExtension.value = '';
|
||||
}
|
||||
|
||||
// Update the propertie
|
||||
await this.fileGroupDropdown.updateProperties({
|
||||
values: fileGroupDdOptions, value: fileGroupDdValue
|
||||
});
|
||||
await this.fileGrowthGroup.updateCssStyles({ 'visibility': visibility });
|
||||
await this.maxSizeGroup.updateCssStyles({ 'margin-top': maxSizeGroupMarginTop });
|
||||
await this.pathContainer.updateCssStyles({ 'margin-top': pathContainerMarginTop });
|
||||
this.fileNameWithExtension.enabled = this.fileSizeInput.enabled = this.isEditingFile && enableInputs;
|
||||
this.autoFilegrowthInput.max = this.inPercentAutogrowth.checked ? fileSizeInputMaxValueInPercent : fileSizeInputMaxValue / 2;
|
||||
this.fileSizeInput.max = fileSizeInputMaxValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the file name with extension on logical name update
|
||||
*/
|
||||
private generateFileNameWithExtension(): string {
|
||||
let fileNameWithExtenstion = this.result.fileNameWithExtension;
|
||||
// if new file, then update the generate the fileNameWithExtenison
|
||||
if (this.result.name !== '' && this.options.isNewFile) {
|
||||
switch (this.result.type) {
|
||||
case localizedConstants.RowsDataFileType:
|
||||
fileNameWithExtenstion = this.result.name + '.ndf';
|
||||
break;
|
||||
case localizedConstants.LogFiletype:
|
||||
fileNameWithExtenstion = this.result.name + '.ldf';
|
||||
break;
|
||||
case localizedConstants.FilestreamFileType:
|
||||
fileNameWithExtenstion = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
return fileNameWithExtenstion;
|
||||
}
|
||||
|
||||
|
||||
public override async onFormFieldChange(): Promise<void> {
|
||||
this.dialogObject.okButton.enabled = JSON.stringify(this.result) !== JSON.stringify(this.options.databaseFile);
|
||||
}
|
||||
|
||||
protected override get dialogResult(): DatabaseFile | undefined {
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -91,26 +91,32 @@ export class DatabaseRoleDialog extends PrincipalDialogBase<DatabaseRoleInfo, Da
|
||||
|
||||
private initializeMemberSection(): void {
|
||||
this.memberTable = this.createTable(localizedConstants.MemberSectionHeader, [localizedConstants.NameText], this.objectInfo.members.map(m => [m]));
|
||||
const buttonContainer = this.addButtonsForTable(this.memberTable, localizedConstants.AddMemberAriaLabel, localizedConstants.RemoveMemberAriaLabel,
|
||||
async () => {
|
||||
const dialog = new FindObjectDialog(this.objectManagementService, {
|
||||
objectTypes: localizedConstants.getObjectTypeInfo([
|
||||
ObjectManagement.NodeType.DatabaseRole,
|
||||
ObjectManagement.NodeType.User
|
||||
]),
|
||||
selectAllObjectTypes: true,
|
||||
multiSelect: true,
|
||||
contextId: this.contextId,
|
||||
title: localizedConstants.SelectDatabaseRoleMemberDialogTitle,
|
||||
showSchemaColumn: false
|
||||
});
|
||||
await dialog.open();
|
||||
const result = await dialog.waitForClose();
|
||||
await this.addMembers(result.selectedObjects.map(r => r.name));
|
||||
const buttonContainer = this.addButtonsForTable(this.memberTable,
|
||||
{
|
||||
buttonAriaLabel: localizedConstants.AddMemberAriaLabel,
|
||||
buttonHandler: async () => {
|
||||
const dialog = new FindObjectDialog(this.objectManagementService, {
|
||||
objectTypes: localizedConstants.getObjectTypeInfo([
|
||||
ObjectManagement.NodeType.DatabaseRole,
|
||||
ObjectManagement.NodeType.User
|
||||
]),
|
||||
selectAllObjectTypes: true,
|
||||
multiSelect: true,
|
||||
contextId: this.contextId,
|
||||
title: localizedConstants.SelectDatabaseRoleMemberDialogTitle,
|
||||
showSchemaColumn: false
|
||||
});
|
||||
await dialog.open();
|
||||
const result = await dialog.waitForClose();
|
||||
await this.addMembers(result.selectedObjects.map(r => r.name));
|
||||
}
|
||||
},
|
||||
async () => {
|
||||
if (this.memberTable.selectedRows.length === 1) {
|
||||
await this.removeMember(this.memberTable.selectedRows[0]);
|
||||
{
|
||||
buttonAriaLabel: localizedConstants.RemoveMemberAriaLabel,
|
||||
buttonHandler: async () => {
|
||||
if (this.memberTable.selectedRows.length === 1) {
|
||||
await this.removeMember(this.memberTable.selectedRows[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.memberSection = this.createGroup(localizedConstants.MemberSectionHeader, [this.memberTable, buttonContainer]);
|
||||
|
||||
@@ -23,7 +23,7 @@ export class DetachDatabaseDialog extends ObjectManagementDialogBase<Database, D
|
||||
}
|
||||
|
||||
protected async initializeUI(): Promise<void> {
|
||||
let tableData = this.viewInfo.files.map(file => [file.name, file.type, file.fileGroup, file.path]);
|
||||
let tableData = this.objectInfo.files.map(file => [file.name, file.type, file.fileGroup, file.path]);
|
||||
let columnNames = [DatabaseFileNameLabel, DatabaseFileTypeLabel, DatabaseFileGroupLabel, DatabaseFilePathLabel];
|
||||
let fileTable = this.createTable(DatabaseFilesLabel, columnNames, tableData);
|
||||
let tableGroup = this.createGroup(DatabaseFilesLabel, [fileTable], false);
|
||||
|
||||
@@ -10,7 +10,7 @@ import * as localizedConstants from '../localizedConstants';
|
||||
import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase';
|
||||
import { FindObjectDialog, FindObjectDialogResult } from './findObjectDialog';
|
||||
import { deepClone } from '../../util/objects';
|
||||
import { DefaultTableWidth, getTableHeight } from '../../ui/dialogBase';
|
||||
import { DefaultTableWidth, DialogButton, getTableHeight } from '../../ui/dialogBase';
|
||||
import { ObjectSelectionMethod, ObjectSelectionMethodDialog } from './objectSelectionMethodDialog';
|
||||
import { DatabaseLevelPrincipalViewInfo, SecurablePermissionItem, SecurablePermissions, SecurityPrincipalObject, SecurityPrincipalViewInfo } from '../interfaces';
|
||||
|
||||
@@ -51,8 +51,15 @@ export abstract class PrincipalDialogBase<ObjectInfoType extends SecurityPrincip
|
||||
securableTableColumns.splice(1, 0, localizedConstants.SchemaText);
|
||||
}
|
||||
this.securableTable = this.createTable(localizedConstants.SecurablesText, securableTableColumns, this.getSecurableTableData());
|
||||
const buttonContainer = this.addButtonsForTable(this.securableTable, localizedConstants.AddSecurableAriaLabel, localizedConstants.RemoveSecurableAriaLabel,
|
||||
(button) => this.onAddSecurableButtonClicked(button), () => this.onRemoveSecurableButtonClicked());
|
||||
const addButtonComponent: DialogButton = {
|
||||
buttonAriaLabel: localizedConstants.AddSecurableAriaLabel,
|
||||
buttonHandler: (button) => this.onAddSecurableButtonClicked(button)
|
||||
};
|
||||
const removeButtonComponent: DialogButton = {
|
||||
buttonAriaLabel: localizedConstants.RemoveSecurableAriaLabel,
|
||||
buttonHandler: () => this.onRemoveSecurableButtonClicked()
|
||||
};
|
||||
const buttonContainer = this.addButtonsForTable(this.securableTable, addButtonComponent, removeButtonComponent);
|
||||
this.disposables.push(this.securableTable.onRowSelected(async () => {
|
||||
await this.updatePermissionsTable();
|
||||
}));
|
||||
|
||||
@@ -96,27 +96,32 @@ export class ServerRoleDialog extends PrincipalDialogBase<ServerRoleInfo, Server
|
||||
|
||||
private initializeMemberSection(): void {
|
||||
this.memberTable = this.createTable(localizedConstants.MemberSectionHeader, [localizedConstants.NameText], this.objectInfo.members.map(m => [m]));
|
||||
const buttonContainer = this.addButtonsForTable(this.memberTable, localizedConstants.AddMemberAriaLabel, localizedConstants.RemoveMemberAriaLabel,
|
||||
async () => {
|
||||
const dialog = new FindObjectDialog(this.objectManagementService, {
|
||||
objectTypes: localizedConstants.getObjectTypeInfo([
|
||||
ObjectManagement.NodeType.ServerLevelLogin,
|
||||
ObjectManagement.NodeType.ServerLevelServerRole
|
||||
]),
|
||||
selectAllObjectTypes: true,
|
||||
multiSelect: true,
|
||||
contextId: this.contextId,
|
||||
title: localizedConstants.SelectServerRoleMemberDialogTitle
|
||||
});
|
||||
await dialog.open();
|
||||
const result = await dialog.waitForClose();
|
||||
await this.addMembers(result.selectedObjects.map(r => r.name));
|
||||
},
|
||||
async () => {
|
||||
const buttonContainer = this.addButtonsForTable(this.memberTable,
|
||||
{
|
||||
buttonAriaLabel: localizedConstants.AddMemberAriaLabel,
|
||||
buttonHandler: async () => {
|
||||
const dialog = new FindObjectDialog(this.objectManagementService, {
|
||||
objectTypes: localizedConstants.getObjectTypeInfo([
|
||||
ObjectManagement.NodeType.ServerLevelLogin,
|
||||
ObjectManagement.NodeType.ServerLevelServerRole
|
||||
]),
|
||||
selectAllObjectTypes: true,
|
||||
multiSelect: true,
|
||||
contextId: this.contextId,
|
||||
title: localizedConstants.SelectServerRoleMemberDialogTitle
|
||||
});
|
||||
await dialog.open();
|
||||
const result = await dialog.waitForClose();
|
||||
await this.addMembers(result.selectedObjects.map(r => r.name));
|
||||
}
|
||||
}, {
|
||||
buttonAriaLabel: localizedConstants.RemoveMemberAriaLabel,
|
||||
buttonHandler: async () => {
|
||||
if (this.memberTable.selectedRows.length === 1) {
|
||||
await this.removeMember(this.memberTable.selectedRows[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
this.memberSection = this.createGroup(localizedConstants.MemberSectionHeader, [this.memberTable, buttonContainer]);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,11 @@ export function getTableHeight(rowCount: number, minRowCount: number = DefaultMi
|
||||
return Math.min(Math.max(rowCount, minRowCount), maxRowCount) * TableRowHeight + TableColumnHeaderHeight;
|
||||
}
|
||||
|
||||
export interface DialogButton {
|
||||
buttonAriaLabel: string;
|
||||
buttonHandler: (button: azdata.ButtonComponent) => Promise<void>
|
||||
}
|
||||
|
||||
export type TableListItemEnabledStateGetter<T> = (item: T) => boolean;
|
||||
export type TableListItemValueGetter<T> = (item: T) => string[];
|
||||
export type TableListItemComparer<T> = (item1: T, item2: T) => boolean;
|
||||
@@ -72,6 +77,8 @@ export abstract class DialogBase<DialogResult> {
|
||||
|
||||
protected onFormFieldChange(): void { }
|
||||
|
||||
protected get removeButtonEnabled(): boolean { return true; }
|
||||
|
||||
protected validateInput(): Promise<string[]> { return Promise.resolve([]); }
|
||||
|
||||
public async open(): Promise<void> {
|
||||
@@ -155,7 +162,7 @@ export abstract class DialogBase<DialogResult> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an input box. If properties are not passed in, then an input box is created with the following default properties:
|
||||
* Creates an input box. If properties are not passed in, then an input box is created with the following default properties:
|
||||
* inputType - text
|
||||
* width - DefaultInputWidth
|
||||
* value - empty
|
||||
@@ -270,28 +277,48 @@ export abstract class DialogBase<DialogResult> {
|
||||
return table;
|
||||
}
|
||||
|
||||
protected addButtonsForTable(table: azdata.TableComponent, addButtonAriaLabel: string, removeButtonAriaLabel: string, addHandler: (button: azdata.ButtonComponent) => Promise<void>, removeHandler: (button: azdata.ButtonComponent) => Promise<void>): azdata.FlexContainer {
|
||||
let addButton: azdata.ButtonComponent;
|
||||
let removeButton: azdata.ButtonComponent;
|
||||
const updateButtons = () => {
|
||||
protected addButtonsForTable(table: azdata.TableComponent, addbutton: DialogButton, removeButton: DialogButton, editButton: DialogButton = undefined): azdata.FlexContainer {
|
||||
let addButtonComponent: azdata.ButtonComponent;
|
||||
let editButtonComponent: azdata.ButtonComponent;
|
||||
let removeButtonComponent: azdata.ButtonComponent;
|
||||
let buttonComponents: azdata.ButtonComponent[] = [];
|
||||
const updateButtons = (isRemoveEnabled: boolean = undefined) => {
|
||||
this.onFormFieldChange();
|
||||
removeButton.enabled = table.selectedRows?.length === 1 && table.selectedRows[0] !== -1 && table.selectedRows[0] < table.data.length;
|
||||
const tableSelectedRowsLengthCheck = table.selectedRows?.length === 1 && table.selectedRows[0] !== -1 && table.selectedRows[0] < table.data.length;
|
||||
if (editButton !== undefined) {
|
||||
editButtonComponent.enabled = tableSelectedRowsLengthCheck;
|
||||
}
|
||||
removeButtonComponent.enabled = !!isRemoveEnabled && tableSelectedRowsLengthCheck;
|
||||
}
|
||||
addButton = this.createButton(uiLoc.AddText, addButtonAriaLabel, async () => {
|
||||
await addHandler(addButton);
|
||||
addButtonComponent = this.createButton(uiLoc.AddText, addbutton.buttonAriaLabel, async () => {
|
||||
await addbutton.buttonHandler(addButtonComponent);
|
||||
updateButtons();
|
||||
});
|
||||
removeButton = this.createButton(uiLoc.RemoveText, removeButtonAriaLabel, async () => {
|
||||
await removeHandler(removeButton);
|
||||
buttonComponents.push(addButtonComponent);
|
||||
|
||||
if (editButton !== undefined) {
|
||||
editButtonComponent = this.createButton(uiLoc.EditText, editButton.buttonAriaLabel, async () => {
|
||||
await editButton.buttonHandler(editButtonComponent);
|
||||
updateButtons();
|
||||
}, false);
|
||||
buttonComponents.push(editButtonComponent);
|
||||
}
|
||||
|
||||
removeButtonComponent = this.createButton(uiLoc.RemoveText, removeButton.buttonAriaLabel, async () => {
|
||||
await removeButton.buttonHandler(removeButtonComponent);
|
||||
if (table.selectedRows.length === 1 && table.selectedRows[0] >= table.data.length) {
|
||||
table.selectedRows = [table.data.length - 1];
|
||||
}
|
||||
updateButtons();
|
||||
}, false);
|
||||
buttonComponents.push(removeButtonComponent);
|
||||
|
||||
this.disposables.push(table.onRowSelected(() => {
|
||||
updateButtons();
|
||||
const isRemoveButtonEnabled = this.removeButtonEnabled;
|
||||
updateButtons(isRemoveButtonEnabled);
|
||||
}));
|
||||
return this.createButtonContainer([addButton, removeButton]);
|
||||
|
||||
return this.createButtonContainer(buttonComponents)
|
||||
}
|
||||
|
||||
protected createDropdown(ariaLabel: string, handler: (newValue: string) => Promise<void>, values: string[], value: string | undefined, enabled: boolean = true, width: number = DefaultInputWidth, editable?: boolean, strictSelection?: boolean): azdata.DropDownComponent {
|
||||
@@ -344,11 +371,12 @@ export abstract class DialogBase<DialogResult> {
|
||||
}).withItems(items, { flex: '0 0 auto' }).component();
|
||||
}
|
||||
|
||||
protected createRadioButton(label: string, groupName: string, checked: boolean, handler: (checked: boolean) => Promise<void>): azdata.RadioButtonComponent {
|
||||
protected createRadioButton(label: string, groupName: string, checked: boolean, handler: (checked: boolean) => Promise<void>, enabled: boolean = true): azdata.RadioButtonComponent {
|
||||
const radio = this.modelView.modelBuilder.radioButton().withProps({
|
||||
label: label,
|
||||
name: groupName,
|
||||
checked: checked
|
||||
checked: checked,
|
||||
enabled: enabled
|
||||
}).component();
|
||||
this.disposables.push(radio.onDidChangeCheckedState(async checked => {
|
||||
await handler(checked);
|
||||
|
||||
@@ -14,6 +14,7 @@ export const LoadingDialogCompletedText: string = localize('mssql.ui.loadingDial
|
||||
export const ScriptText: string = localize('mssql.ui.scriptText', "Script");
|
||||
export const SelectText = localize('objectManagement.selectLabel', "Select");
|
||||
export const AddText = localize('objectManagement.addText', "Add…");
|
||||
export const EditText = localize('objectManagement.editText', "Edit");
|
||||
export const RemoveText = localize('objectManagement.removeText', "Remove");
|
||||
export const NoActionScriptedMessage: string = localize('mssql.ui.noActionScriptedMessage', "There is no action to be scripted.");
|
||||
export const ScriptGeneratedText: string = localize('mssql.ui.scriptGenerated', "Script has been generated successfully. You can close the dialog to view it in the newly opened editor.")
|
||||
|
||||
Reference in New Issue
Block a user