diff --git a/extensions/mssql/src/mssql.d.ts b/extensions/mssql/src/mssql.d.ts index b5a64b407c..fdc1f1b1e9 100644 --- a/extensions/mssql/src/mssql.d.ts +++ b/extensions/mssql/src/mssql.d.ts @@ -877,6 +877,7 @@ declare module 'mssql' { DatabaseRole = "DatabaseRole", ServerLevelLogin = "ServerLevelLogin", ServerLevelServerRole = "ServerLevelServerRole", + Server = "Server", Table = "Table", User = "User", View = "View" diff --git a/extensions/mssql/src/objectManagement/commands.ts b/extensions/mssql/src/objectManagement/commands.ts index 0994560074..a6285ffd12 100644 --- a/extensions/mssql/src/objectManagement/commands.ts +++ b/extensions/mssql/src/objectManagement/commands.ts @@ -22,6 +22,7 @@ import { ServerRoleDialog } from './ui/serverRoleDialog'; import { DatabaseRoleDialog } from './ui/databaseRoleDialog'; import { ApplicationRoleDialog } from './ui/applicationRoleDialog'; import { DatabaseDialog } from './ui/databaseDialog'; +import { ServerPropertiesDialog } from './ui/serverPropertiesDialog'; export function registerObjectManagementCommands(appContext: AppContext) { // Notes: Change the second parameter to false to use the actual object management service. @@ -107,14 +108,18 @@ async function handleObjectPropertiesDialogCommand(context: azdata.ObjectExplore } try { const parentUrn = context.nodeInfo ? await getParentUrn(context) : undefined; + const objectType = context.nodeInfo ? context.nodeInfo.nodeType as ObjectManagement.NodeType : (context.connectionProfile.databaseName === '' ? ObjectManagement.NodeType.Server : ObjectManagement.NodeType.Database); + const objectName = context.nodeInfo ? context.nodeInfo.label : objectManagementLoc.PropertiesHeader; + const objectUrn = context.nodeInfo ? context.nodeInfo!.metadata!.urn : undefined; + const options: ObjectManagementDialogOptions = { connectionUri: connectionUri, isNewObject: false, database: context.connectionProfile!.databaseName!, - objectType: context.nodeInfo.nodeType as ObjectManagement.NodeType, - objectName: context.nodeInfo.label, + objectType: objectType, + objectName: objectName, parentUrn: parentUrn, - objectUrn: context.nodeInfo!.metadata!.urn, + objectUrn: objectUrn, objectExplorerContext: context }; const dialog = getDialog(service, options); @@ -242,6 +247,8 @@ function getDialog(service: IObjectManagementService, dialogOptions: ObjectManag return new LoginDialog(service, dialogOptions); case ObjectManagement.NodeType.ServerLevelServerRole: return new ServerRoleDialog(service, dialogOptions); + case ObjectManagement.NodeType.Server: + return new ServerPropertiesDialog(service, dialogOptions); case ObjectManagement.NodeType.User: return new UserDialog(service, dialogOptions); case ObjectManagement.NodeType.Database: diff --git a/extensions/mssql/src/objectManagement/constants.ts b/extensions/mssql/src/objectManagement/constants.ts index fc8b63a08b..52fe8abcf9 100644 --- a/extensions/mssql/src/objectManagement/constants.ts +++ b/extensions/mssql/src/objectManagement/constants.ts @@ -28,6 +28,7 @@ export const AlterApplicationRoleDocUrl = 'https://learn.microsoft.com/sql/t-sql export const CreateDatabaseRoleDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/create-role-transact-sql'; export const AlterDatabaseRoleDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/alter-role-transact-sql'; export const CreateDatabaseDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/create-database-transact-sql'; +export const ViewServerPropertiesDocUrl = 'https://learn.microsoft.com/sql/t-sql/functions/serverproperty-transact-sql'; export const DatabasePropertiesDocUrl = 'https://learn.microsoft.com/sql/relational-databases/databases/database-properties-general-page'; export const enum TelemetryActions { @@ -40,3 +41,5 @@ export const enum TelemetryActions { } export const ObjectManagementViewName = 'ObjectManagement'; + +export const AzureSQLMI = 'Azure SQL Database Managed Instance'; diff --git a/extensions/mssql/src/objectManagement/interfaces.ts b/extensions/mssql/src/objectManagement/interfaces.ts index eabb782390..36331346a7 100644 --- a/extensions/mssql/src/objectManagement/interfaces.ts +++ b/extensions/mssql/src/objectManagement/interfaces.ts @@ -463,3 +463,28 @@ export interface AzureEditionDetails { editionDisplayName: string; details: string[]; } + +export interface Server extends ObjectManagement.SqlObject { + hardwareGeneration: string; + language: string; + memoryInMB: number; + operatingSystem: string; + platform: string; + processors: string; + version: string; + isClustered: boolean; + isHadrEnabled: boolean; + isPolyBaseInstalled: boolean; + isXTPSupported: boolean; + product: string; + reservedStorageSizeMB: number; + rootDirectory: string; + serverCollation: string; + serviceTier: string; + storageSpaceUsageInGB: number; + minServerMemory: number; + maxServerMemory: number; +} + +export interface ServerViewInfo extends ObjectManagement.ObjectViewInfo { +} diff --git a/extensions/mssql/src/objectManagement/localizedConstants.ts b/extensions/mssql/src/objectManagement/localizedConstants.ts index 4d17a00dcb..fa6ee056db 100644 --- a/extensions/mssql/src/objectManagement/localizedConstants.ts +++ b/extensions/mssql/src/objectManagement/localizedConstants.ts @@ -20,6 +20,7 @@ export const ColumnTypeDisplayName: string = localize('objectManagement.ColumnDi export const DatabaseTypeDisplayName: string = localize('objectManagement.DatabaseDisplayName', "database"); export const ServerRoleTypeDisplayName: string = localize('objectManagement.ServerRoleTypeDisplayName', "server role"); export const ServerRoleTypeDisplayNameInTitle: string = localize('objectManagement.ServerRoleTypeDisplayNameInTitle', "Server Role"); +export const ServerTypeDisplayName: string = localize('objectManagement.ServerDisplayName', "Server"); export const ApplicationRoleTypeDisplayName: string = localize('objectManagement.ApplicationRoleTypeDisplayName', "application role"); export const ApplicationRoleTypeDisplayNameInTitle: string = localize('objectManagement.ApplicationRoleTypeDisplayNameInTitle', "Application Role"); export const DatabaseRoleTypeDisplayName: string = localize('objectManagement.DatabaseRoleTypeDisplayName', "database role"); @@ -222,6 +223,29 @@ export const ObjectSelectionMethodDialog_AllObjectsOfTypes = localize('objectMan export const ObjectSelectionMethodDialog_AllObjectsOfSchema = localize('objectManagement.ObjectSelectionMethodDialog_AllObjectsOfSchema', "All objects belonging to a schema"); export const ObjectSelectionMethodDialog_SelectSchemaDropdownLabel = localize('objectManagement.ObjectSelectionMethodDialog_SelectSchemaDropdownLabel', "Schema"); +// Server Properties Dialog +export const PropertiesHeader = localize('objectManagement.properties', "Properties"); +export const HardwareGenerationText = localize('objectManagement.hardwareGeneration', "Hardware Generation"); +export const LanguageText = localize('objectManagement.language', "Language"); +export const MemoryText = localize('objectManagement.memory', "Memory"); +export const OperatingSystemText = localize('objectManagement.operatingSystem', "Operating System"); +export const PlatformText = localize('objectManagement.platform', "Platform"); +export const ProcessorsText = localize('objectManagement.processors', "Processors"); +export const IsClusteredText = localize('objectManagement.isClustered', "Is Clustered"); +export const IsHadrEnabledText = localize('objectManagement.isHadrEnabled', "Is HADR Enabled"); +export const IsPolyBaseInstalledText = localize('objectManagement.isPolyBaseInstalled', "Is PolyBase Installed"); +export const IsXTPSupportedText = localize('objectManagement.isXTPSupported', "Is XTP Supported"); +export const ProductText = localize('objectManagement.product', "Product"); +export const ReservedStorageSizeInMBText = localize('objectManagement.reservedStorageSizeInMB', "Reserved Storage Size"); +export const RootDirectoryText = localize('objectManagement.rootDirectory', "Root Directory"); +export const ServerCollationText = localize('objectManagement.serverCollation', "Server Collation"); +export const ServiceTierText = localize('objectManagement.serviceTier', "Service Tier"); +export const StorageSpaceUsageInGBText = localize('objectManagement.storageSpaceUsageInGB', "Storage Space Usage"); +export const VersionText = localize('objectManagement.versionText', "Version"); + + +export const minServerMemoryText = localize('objectManagement.minServerMemoryText', "Minimum Server Memory (MB)"); +export const maxServerMemoryText = localize('objectManagement.maxServerMemoryText', "Maximum Server Memory (MB)"); //Database properties Dialog export const LastDatabaseBackupText = localize('objectManagement.lastDatabaseBackup', "Last Database Backup"); export const LastDatabaseLogBackupText = localize('objectManagement.lastDatabaseLogBackup', "Last Database Log Backup"); @@ -249,6 +273,8 @@ export function getNodeTypeDisplayName(type: string, inTitle: boolean = false): return inTitle ? LoginTypeDisplayNameInTitle : LoginTypeDisplayName; case ObjectManagement.NodeType.ServerLevelServerRole: return inTitle ? ServerRoleTypeDisplayNameInTitle : ServerRoleTypeDisplayName; + case ObjectManagement.NodeType.Server: + return ServerTypeDisplayName; case ObjectManagement.NodeType.User: return inTitle ? UserTypeDisplayNameInTitle : UserTypeDisplayName; case ObjectManagement.NodeType.Table: diff --git a/extensions/mssql/src/objectManagement/objectManagementService.ts b/extensions/mssql/src/objectManagement/objectManagementService.ts index b08970010b..4258c73dd9 100644 --- a/extensions/mssql/src/objectManagement/objectManagementService.ts +++ b/extensions/mssql/src/objectManagement/objectManagementService.ts @@ -474,3 +474,4 @@ export class TestObjectManagementService implements IObjectManagementService { }); } } + diff --git a/extensions/mssql/src/objectManagement/ui/serverPropertiesDialog.ts b/extensions/mssql/src/objectManagement/ui/serverPropertiesDialog.ts new file mode 100644 index 0000000000..7933d3f3e6 --- /dev/null +++ b/extensions/mssql/src/objectManagement/ui/serverPropertiesDialog.ts @@ -0,0 +1,172 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IObjectManagementService } from 'mssql'; +import * as localizedConstants from '../localizedConstants'; +import { AzureSQLMI, ViewServerPropertiesDocUrl } from '../constants'; +import { Server, ServerViewInfo } from '../interfaces'; + +export class ServerPropertiesDialog extends ObjectManagementDialogBase { + private generalTab: azdata.Tab; + private platformSection: azdata.GroupContainer; + private sqlServerSection: azdata.GroupContainer; + private nameInput: azdata.InputBoxComponent; + private hardwareGenerationInput: azdata.InputBoxComponent; + private languageDropdown: azdata.DropDownComponent; + private memoryInput: azdata.InputBoxComponent; + private operatingSystemInput: azdata.InputBoxComponent; + private platformInput: azdata.InputBoxComponent; + private processorsInput: azdata.InputBoxComponent; + private isClusteredInput: azdata.InputBoxComponent; + private isHadrEnabledInput: azdata.InputBoxComponent; + private isPolyBaseInstalledInput: azdata.InputBoxComponent; + private isXTPSupportedInput: azdata.InputBoxComponent; + private productInput: azdata.InputBoxComponent; + private reservedStorageSizeInMBInput: azdata.InputBoxComponent; + private rootDirectoryInput: azdata.InputBoxComponent; + private serverCollationInput: azdata.InputBoxComponent; + private serviceTierInput: azdata.InputBoxComponent; + private storageSpaceUsageInGBInput: azdata.InputBoxComponent; + private versionInput: azdata.InputBoxComponent; + + private memoryTab: azdata.Tab; + private memorySection: azdata.GroupContainer; + private minServerMemoryInput: azdata.InputBoxComponent; + private maxServerMemoryInput: azdata.InputBoxComponent; + + constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) { + super(objectManagementService, options); + } + + protected override get helpUrl(): string { + return ViewServerPropertiesDocUrl; + } + + protected async initializeUI(): Promise { + this.initializeGeneralSection(); + this.initializeMemorySection(); + const serverPropertiesTabGroup = { title: '', tabs: [this.generalTab, this.memoryTab] }; + const serverPropertiesTabbedPannel = this.modelView.modelBuilder.tabbedPanel() + .withTabs([serverPropertiesTabGroup]) + .withProps({ + CSSStyles: { + 'margin': '-10px 0px 0px -10px' + } + }).component(); + this.formContainer.addItem(serverPropertiesTabbedPannel); + } + + private initializeGeneralSection(): void { + this.nameInput = this.createInputBox(localizedConstants.NameText, async (newValue) => { + this.objectInfo.name = newValue; + }, this.objectInfo.name, this.options.isNewObject); + const nameContainer = this.createLabelInputContainer(localizedConstants.NameText, this.nameInput); + + this.hardwareGenerationInput = this.createInputBox(localizedConstants.HardwareGenerationText, async () => { }, this.objectInfo.hardwareGeneration.toString(), this.options.isNewObject); + const hardwareGenerationContainer = this.createLabelInputContainer(localizedConstants.HardwareGenerationText, this.hardwareGenerationInput); + + this.languageDropdown = this.createDropdown(localizedConstants.LanguageText, async () => { }, [this.objectInfo.language], this.objectInfo.language, this.options.isNewObject); + const languageContainer = this.createLabelInputContainer(localizedConstants.LanguageText, this.languageDropdown); + + this.memoryInput = this.createInputBox(localizedConstants.MemoryText, async () => { }, this.objectInfo.memoryInMB.toString().concat(' MB'), this.options.isNewObject); + const memoryContainer = this.createLabelInputContainer(localizedConstants.MemoryText, this.memoryInput); + + this.operatingSystemInput = this.createInputBox(localizedConstants.OperatingSystemText, async () => { }, this.objectInfo.operatingSystem, this.options.isNewObject); + const operatingSystemContainer = this.createLabelInputContainer(localizedConstants.OperatingSystemText, this.operatingSystemInput); + + this.platformInput = this.createInputBox(localizedConstants.PlatformText, async () => { }, this.objectInfo.platform, this.options.isNewObject); + const platformContainer = this.createLabelInputContainer(localizedConstants.PlatformText, this.platformInput); + + this.processorsInput = this.createInputBox(localizedConstants.ProcessorsText, async () => { }, this.objectInfo.processors, this.options.isNewObject); + const processorsContainer = this.createLabelInputContainer(localizedConstants.ProcessorsText, this.processorsInput); + + this.isClusteredInput = this.createInputBox(localizedConstants.IsClusteredText, async () => { }, this.objectInfo.isClustered.toString(), this.options.isNewObject); + const isClusteredContainer = this.createLabelInputContainer(localizedConstants.IsClusteredText, this.isClusteredInput); + + this.isHadrEnabledInput = this.createInputBox(localizedConstants.IsHadrEnabledText, async () => { }, this.objectInfo.isHadrEnabled.toString(), this.options.isNewObject); + const isHadrEnabledContainer = this.createLabelInputContainer(localizedConstants.IsHadrEnabledText, this.isHadrEnabledInput); + + this.isPolyBaseInstalledInput = this.createInputBox(localizedConstants.IsPolyBaseInstalledText, async () => { }, this.objectInfo.isPolyBaseInstalled.toString(), this.options.isNewObject); + const isPolyBaseInstalledContainer = this.createLabelInputContainer(localizedConstants.IsPolyBaseInstalledText, this.isPolyBaseInstalledInput); + + this.isXTPSupportedInput = this.createInputBox(localizedConstants.IsXTPSupportedText, async () => { }, this.objectInfo.isXTPSupported.toString(), this.options.isNewObject); + const isXTPSupportedContainer = this.createLabelInputContainer(localizedConstants.IsXTPSupportedText, this.isXTPSupportedInput); + + this.productInput = this.createInputBox(localizedConstants.ProductText, async () => { }, this.objectInfo.product, this.options.isNewObject); + const productContainer = this.createLabelInputContainer(localizedConstants.ProductText, this.productInput); + + this.reservedStorageSizeInMBInput = this.createInputBox(localizedConstants.ReservedStorageSizeInMBText, async () => { }, this.objectInfo.reservedStorageSizeMB.toString().concat(' MB'), this.options.isNewObject); + const reservedStorageSizeInMBContainer = this.createLabelInputContainer(localizedConstants.ReservedStorageSizeInMBText, this.reservedStorageSizeInMBInput); + + this.rootDirectoryInput = this.createInputBox(localizedConstants.RootDirectoryText, async () => { }, this.objectInfo.rootDirectory, this.options.isNewObject); + const rootDirectoryContainer = this.createLabelInputContainer(localizedConstants.RootDirectoryText, this.rootDirectoryInput); + + this.serverCollationInput = this.createInputBox(localizedConstants.ServerCollationText, async () => { }, this.objectInfo.serverCollation, this.options.isNewObject); + const serverCollationContainer = this.createLabelInputContainer(localizedConstants.ServerCollationText, this.serverCollationInput); + + this.serviceTierInput = this.createInputBox(localizedConstants.ServiceTierText, async () => { }, this.objectInfo.serviceTier, this.options.isNewObject); + const serviceTierContainer = this.createLabelInputContainer(localizedConstants.ServiceTierText, this.serviceTierInput); + + this.storageSpaceUsageInGBInput = this.createInputBox(localizedConstants.StorageSpaceUsageInGBText, async () => { }, this.objectInfo.storageSpaceUsageInGB.toString().concat(' GB'), this.options.isNewObject); + const storageSpaceUsageInGbContainer = this.createLabelInputContainer(localizedConstants.StorageSpaceUsageInGBText, this.storageSpaceUsageInGBInput); + + this.versionInput = this.createInputBox(localizedConstants.VersionText, async () => { }, this.objectInfo.version, this.options.isNewObject); + const versionContainer = this.createLabelInputContainer(localizedConstants.VersionText, this.versionInput); + + let platformItems = [ + nameContainer, + languageContainer, + memoryContainer, + operatingSystemContainer, + platformContainer, + processorsContainer + ]; + + let sqlServerItems = [ + isClusteredContainer, + isHadrEnabledContainer, + isPolyBaseInstalledContainer, + isXTPSupportedContainer, + productContainer, + rootDirectoryContainer, + serverCollationContainer, + versionContainer + ]; + + if (this.objectInfo.platform === AzureSQLMI) { + platformItems.unshift(hardwareGenerationContainer); + sqlServerItems.push(reservedStorageSizeInMBContainer, serviceTierContainer, storageSpaceUsageInGbContainer); + // remove isXTPSupported + sqlServerItems.splice(3, 1); + } + + this.platformSection = this.createGroup('Platform', platformItems, true); + this.sqlServerSection = this.createGroup('SQL Server', sqlServerItems, true); + + const generalContainer = this.createGroup('', [this.platformSection, this.sqlServerSection]) + + this.generalTab = this.createTab('generalId', localizedConstants.GeneralSectionHeader, generalContainer); + } + + private initializeMemorySection(): void { + this.minServerMemoryInput = this.createInputBox(localizedConstants.minServerMemoryText, async (newValue) => { + this.objectInfo.minServerMemory = +newValue; + }, this.objectInfo.minServerMemory.toString(), true, 'number'); + const minMemoryContainer = this.createLabelInputContainer(localizedConstants.minServerMemoryText, this.minServerMemoryInput); + + this.maxServerMemoryInput = this.createInputBox(localizedConstants.maxServerMemoryText, async (newValue) => { + this.objectInfo.maxServerMemory = +newValue; + }, this.objectInfo.maxServerMemory.toString(), true, 'number'); + const maxMemoryContainer = this.createLabelInputContainer(localizedConstants.maxServerMemoryText, this.maxServerMemoryInput); + + this.memorySection = this.createGroup('', [ + minMemoryContainer, + maxMemoryContainer + ], false); + + this.memoryTab = this.createTab('memoryId', localizedConstants.MemoryText, this.memorySection); + } +} diff --git a/extensions/mssql/src/ui/dialogBase.ts b/extensions/mssql/src/ui/dialogBase.ts index 89e7faff1d..4dd54ce60e 100644 --- a/extensions/mssql/src/ui/dialogBase.ts +++ b/extensions/mssql/src/ui/dialogBase.ts @@ -165,6 +165,14 @@ export abstract class DialogBase { }).withItems(items).component(); } + protected createTab(id: string, title: string, content?: azdata.Component): azdata.Tab { + return { + title: title, + content: content, + id: id + }; + } + protected createTableList(ariaLabel: string, columnNames: string[], allItems: T[],