add securable settings (#22936)

* wip

* Update typings

* nullable

* update test service

* support securables

* updata test data

* fix issues

* fix build failure

* update test mocks

* fix typo

* fix reference

* fix findobjectdialog issue

* update SearchResultItem type

* fix table component perf issue

* hide effective permission for server role

* hide effective permission for app role and db role

* vbump sts and fix a couple issues

* STS update and UI update

* fix user login display issue

* vbump sts
This commit is contained in:
Alan Ren
2023-05-15 15:01:57 -07:00
committed by GitHub
parent 25318a648e
commit b56f2ccb60
21 changed files with 693 additions and 178 deletions

View File

@@ -199,7 +199,8 @@ export function createViewContext(): ViewTestContext {
data: [] as any[][], data: [] as any[][],
columns: [] as string[], columns: [] as string[],
onRowSelected: onClick.event, onRowSelected: onClick.event,
appendData: (data: any[][]) => undefined, appendData: (_data: any[][]) => undefined,
setActiveCell: (_row: number, _column: number) => undefined
}); });
let loadingComponent: () => azdata.LoadingComponent = () => Object.assign({}, componentBase, { let loadingComponent: () => azdata.LoadingComponent = () => Object.assign({}, componentBase, {

View File

@@ -301,6 +301,9 @@ export class MockTableComponent extends MockUIComponent implements azdata.TableC
appendData(data: any[][]): Thenable<void> { appendData(data: any[][]): Thenable<void> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
setActiveCell(row: number, column: number): void {
throw new Error('Method not implemented.');
}
} }
export class MockDeclarativeTableComponent extends MockUIComponent implements azdata.DeclarativeTableComponent { export class MockDeclarativeTableComponent extends MockUIComponent implements azdata.DeclarativeTableComponent {

View File

@@ -1,6 +1,6 @@
{ {
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/{#version#}/microsoft.sqltools.servicelayer-{#fileName#}", "downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
"version": "4.8.0.5", "version": "4.8.0.7",
"downloadFileNames": { "downloadFileNames": {
"Windows_86": "win-x86-net7.0.zip", "Windows_86": "win-x86-net7.0.zip",
"Windows_64": "win-x64-net7.0.zip", "Windows_64": "win-x64-net7.0.zip",

View File

@@ -917,7 +917,14 @@ declare module 'mssql' {
} }
/** /**
* Base interface for the object view information * Base interface for all the security principal objects. e.g. Login, Server Role, Database Role...
*/
export interface SecurityPrincipalObject extends SqlObject {
securablePermissions: SecurablePermissions[];
}
/**
* Base interface for the object view information.
*/ */
export interface ObjectViewInfo<T extends SqlObject> { export interface ObjectViewInfo<T extends SqlObject> {
/** /**
@@ -926,10 +933,52 @@ declare module 'mssql' {
objectInfo: T; objectInfo: T;
} }
/**
* Securable type metadata.
*/
export interface SecurableTypeMetadata {
/**
* Name of the securable type.
*/
name: string;
/**
* Display name of the securable type.
*/
displayName: string;
/**
* Permissions supported by the securable type.
*/
permissions: PermissionMetadata[];
}
/**
* Permission metadata.
*/
export interface PermissionMetadata {
/**
* Name of the permission.
*/
name: string;
/**
* Display name of the permission.
*/
displayName: string;
}
/**
* Base interface for security principal object's view information.
*/
export interface SecurityPrincipalViewInfo<T extends SecurityPrincipalObject> extends ObjectViewInfo<T> {
/**
* The securable types that the security principal object can be granted permissions on.
*/
supportedSecurableTypes: SecurableTypeMetadata[];
}
/** /**
* Server level login. * Server level login.
*/ */
export interface Login extends SqlObject { export interface Login extends SecurityPrincipalObject {
/** /**
* Authentication type. * Authentication type.
*/ */
@@ -1025,7 +1074,7 @@ declare module 'mssql' {
/** /**
* The information required to render the login view. * The information required to render the login view.
*/ */
export interface LoginViewInfo extends ObjectViewInfo<Login> { export interface LoginViewInfo extends SecurityPrincipalViewInfo<Login> {
/** /**
* The authentication types supported by the server. * The authentication types supported by the server.
*/ */
@@ -1062,20 +1111,24 @@ declare module 'mssql' {
/** /**
* The permission information a principal has on a securable. * The permission information a principal has on a securable.
*/ */
export interface Permission { export interface SecurablePermissionItem {
/** /**
* Name of the permission. * name of the permission.
*/ */
name: string; permission: string;
/** /**
* Whether the permission is granted or denied. * Name of the grantor.
*/ */
grant: boolean; grantor: string;
/**
* Whether the permission is granted or denied. Undefined means not specified.
*/
grant?: boolean;
/** /**
* Whether the pincipal can grant this permission to other principals. * Whether the pincipal can grant this permission to other principals.
* The value will be ignored if the grant property is set to false. * The value will be ignored if the grant property is set to false.
*/ */
withGrant: boolean; withGrant?: boolean;
} }
/** /**
@@ -1083,13 +1136,25 @@ declare module 'mssql' {
*/ */
export interface SecurablePermissions { export interface SecurablePermissions {
/** /**
* The securable. * The securable name.
*/ */
securable: SqlObject; name: string;
/** /**
* The Permissions. * The securable type.
*/ */
permissions: Permission[]; type: string;
/**
* The schema name of the object if applicable.
*/
schema?: string;
/**
* The permissions.
*/
permissions: SecurablePermissionItem[];
/**
* The effective permissions. Includes all permissions granted to the principal, including those granted through role memberships.
*/
effectivePermissions: string[];
} }
/** /**
@@ -1135,7 +1200,7 @@ declare module 'mssql' {
/** /**
* Database user. * Database user.
*/ */
export interface User extends SqlObject { export interface User extends SecurityPrincipalObject {
/** /**
* Type of the user. * Type of the user.
*/ */
@@ -1172,7 +1237,7 @@ declare module 'mssql' {
/** /**
* The information required to render the user view. * The information required to render the user view.
*/ */
export interface UserViewInfo extends ObjectViewInfo<User> { export interface UserViewInfo extends SecurityPrincipalViewInfo<User> {
/** /**
* All user types supported by the database. * All user types supported by the database.
*/ */
@@ -1198,7 +1263,7 @@ declare module 'mssql' {
/** /**
* Interface representing the server role object. * Interface representing the server role object.
*/ */
export interface ServerRoleInfo extends SqlObject { export interface ServerRoleInfo extends SecurityPrincipalObject {
/** /**
* Name of the server principal that owns the server role. * Name of the server principal that owns the server role.
*/ */
@@ -1216,7 +1281,7 @@ declare module 'mssql' {
/** /**
* Interface representing the information required to render the server role view. * Interface representing the information required to render the server role view.
*/ */
export interface ServerRoleViewInfo extends ObjectViewInfo<ServerRoleInfo> { export interface ServerRoleViewInfo extends SecurityPrincipalViewInfo<ServerRoleInfo> {
/** /**
* Whether the server role is a fixed role. * Whether the server role is a fixed role.
*/ */
@@ -1230,7 +1295,7 @@ declare module 'mssql' {
/** /**
* Interface representing the application role object. * Interface representing the application role object.
*/ */
export interface ApplicationRoleInfo extends SqlObject { export interface ApplicationRoleInfo extends SecurityPrincipalObject {
/** /**
* Default schema of the application role. * Default schema of the application role.
*/ */
@@ -1248,7 +1313,7 @@ declare module 'mssql' {
/** /**
* Interface representing the information required to render the application role view. * Interface representing the information required to render the application role view.
*/ */
export interface ApplicationRoleViewInfo extends ObjectViewInfo<ApplicationRoleInfo> { export interface ApplicationRoleViewInfo extends SecurityPrincipalViewInfo<ApplicationRoleInfo> {
/** /**
* List of all the schemas in the database. * List of all the schemas in the database.
*/ */
@@ -1258,7 +1323,7 @@ declare module 'mssql' {
/** /**
* Interface representing the database role object. * Interface representing the database role object.
*/ */
export interface DatabaseRoleInfo extends SqlObject { export interface DatabaseRoleInfo extends SecurityPrincipalObject {
/** /**
* Name of the database principal that owns the database role. * Name of the database principal that owns the database role.
*/ */
@@ -1276,7 +1341,7 @@ declare module 'mssql' {
/** /**
* Interface representing the information required to render the database role view. * Interface representing the information required to render the database role view.
*/ */
export interface DatabaseRoleViewInfo extends ObjectViewInfo<DatabaseRoleInfo> { export interface DatabaseRoleViewInfo extends SecurityPrincipalViewInfo<DatabaseRoleInfo> {
/** /**
* List of all the schemas in the database. * List of all the schemas in the database.
*/ */
@@ -1294,7 +1359,7 @@ declare module 'mssql' {
/** /**
* type of the object. * type of the object.
*/ */
type: NodeType; type: string;
/** /**
* schema of the object. * schema of the object.
*/ */
@@ -1369,7 +1434,7 @@ declare module 'mssql' {
* @param searchText Search text. * @param searchText Search text.
* @param schema Schema to search in. * @param schema Schema to search in.
*/ */
search(contextId: string, objectTypes: ObjectManagement.NodeType[], searchText?: string, schema?: string): Thenable<ObjectManagement.SearchResultItem[]>; search(contextId: string, objectTypes: string[], searchText?: string, schema?: string): Thenable<ObjectManagement.SearchResultItem[]>;
} }
// Object Management - End. // Object Management - End.
} }

View File

@@ -30,9 +30,22 @@ export const RenameObjectDialogTitle: string = localize('objectManagement.rename
export const OwnerText: string = localize('objectManagement.ownerText', "Owner"); export const OwnerText: string = localize('objectManagement.ownerText', "Owner");
export const BrowseText = localize('objectManagement.browseText', "Browse…"); export const BrowseText = localize('objectManagement.browseText', "Browse…");
export const BrowseOwnerButtonAriaLabel = localize('objectManagement.browseForOwnerText', "Browse for an owner"); export const BrowseOwnerButtonAriaLabel = localize('objectManagement.browseForOwnerText', "Browse for an owner");
export const AddMemberAriaLabel = localize('objectManagement.addMemberText', "Add a member"); export const AddMemberAriaLabel = localize('objectManagement.addMembersText', "Add members");
export const RemoveMemberAriaLabel = localize('objectManagement.removeMemberText', "Remove selected member"); export const RemoveMemberAriaLabel = localize('objectManagement.removeMemberText', "Remove selected member");
export const AddSecurableAriaLabel = localize('objectManagement.addSecurablesText', "Add securables");
export const RemoveSecurableAriaLabel = localize('objectManagement.removeSecurablesText', "Remove selected securable");
export const SecurablesText = localize('objectManagement.securablesText', "Securables");
export const ExplicitPermissionsTableLabel = localize('objectManagement.explicitPermissionsTableLabel', "Explicit permissions for selected securable");
export const EffectivePermissionsTableLabel = localize('objectManagement.effectivePermissionsTableLabel', "Effective permissions for selected securable");
export const PermissionColumnHeader = localize('objectManagement.permissionColumnHeader', "Permission");
export const GrantorColumnHeader = localize('objectManagement.grantorColumnHeader', "Grantor");
export const GrantColumnHeader = localize('objectManagement.grantColumnHeader', "Grant");
export const WithGrantColumnHeader = localize('objectManagement.withGrantColumnHeader', "With Grant");
export const DenyColumnHeader = localize('objectManagement.denyColumnHeader', "Deny");
export const SelectSecurablesDialogTitle = localize('objectManagement.selectSecurablesDialogTitle', "Select Securables");
export function ExplicitPermissionsTableLabelSelected(name: string): string { return localize('objectManagement.explicitPermissionsTableLabelSelected', "Explicit permissions for: {0}", name); }
export function EffectivePermissionsTableLabelSelected(name: string): string { return localize('objectManagement.effectivePermissionsTableLabelSelected', "Effective permissions for: {0}", name); }
export function RefreshObjectExplorerError(error: string): string { export function RefreshObjectExplorerError(error: string): string {
return localize({ return localize({
@@ -133,12 +146,15 @@ export const LoginNotSelectedError = localize('objectManagement.loginNotSelected
export const MembershipSectionHeader = localize('objectManagement.membershipLabel', "Membership"); export const MembershipSectionHeader = localize('objectManagement.membershipLabel', "Membership");
export const MemberSectionHeader = localize('objectManagement.membersLabel', "Members"); export const MemberSectionHeader = localize('objectManagement.membersLabel', "Members");
export const SchemaText = localize('objectManagement.schemaLabel', "Schema"); export const SchemaText = localize('objectManagement.schemaLabel', "Schema");
// Database
export const DatabaseExistsError = (dbName: string) => localize('objectManagement.databaseExistsError', "Database '{0}' already exists. Choose a different database name.", dbName); export const DatabaseExistsError = (dbName: string) => localize('objectManagement.databaseExistsError', "Database '{0}' already exists. Choose a different database name.", dbName);
export const CollationText = localize('objectManagement.collationLabel', "Collation"); export const CollationText = localize('objectManagement.collationLabel', "Collation");
export const RecoveryModelText = localize('objectManagement.recoveryModelLabel', "Recovery Model"); export const RecoveryModelText = localize('objectManagement.recoveryModelLabel', "Recovery Model");
export const CompatibilityLevelText = localize('objectManagement.compatibilityLevelLabel', "Compatibility Level"); export const CompatibilityLevelText = localize('objectManagement.compatibilityLevelLabel', "Compatibility Level");
export const ContainmentTypeText = localize('objectManagement.containmentTypeLabel', "Containment Type"); export const ContainmentTypeText = localize('objectManagement.containmentTypeLabel', "Containment Type");
// Login // Login
export const BlankPasswordConfirmationText: string = localize('objectManagement.blankPasswordConfirmation', "Creating a login with a blank password is a security risk. Are you sure you want to continue?"); export const BlankPasswordConfirmationText: string = localize('objectManagement.blankPasswordConfirmation', "Creating a login with a blank password is a security risk. Are you sure you want to continue?");
export const DeleteLoginConfirmationText: string = localize('objectManagement.deleteLoginConfirmation', "Deleting server logins does not delete the database users associated with the logins. To complete the process, delete the users in each database. It may be necessary to first transfer the ownership of schemas to new users."); export const DeleteLoginConfirmationText: string = localize('objectManagement.deleteLoginConfirmation', "Deleting server logins does not delete the database users associated with the logins. To complete the process, delete the users in each database. It may be necessary to first transfer the ownership of schemas to new users.");

View File

@@ -67,6 +67,126 @@ export class ObjectManagementService extends BaseService implements IObjectManag
} }
} }
const ServerLevelSecurableTypes: ObjectManagement.SecurableTypeMetadata[] = [
{
name: 'Server',
displayName: 'Server',
permissions: [{
name: 'CONNECT SQL',
displayName: 'CONNECT SQL'
}, {
name: 'VIEW ANY DATABASE',
displayName: 'VIEW ANY DATABASE'
}]
}, {
name: 'ServerRole',
displayName: 'Server Role',
permissions: [{
name: 'ALTER',
displayName: 'ALTER'
}, {
name: 'CONTROL',
displayName: 'CONTROL'
}, {
name: 'TAKE OWNERSHIP',
displayName: 'TAKE OWNERSHIP'
}]
}
];
const DatabaseLevelSecurableTypes: ObjectManagement.SecurableTypeMetadata[] = [
{
name: 'AggregateFunction',
displayName: 'Aggregate Function',
permissions: [{
name: 'EXECUTE',
displayName: 'EXECUTE'
}, {
name: 'ALTER',
displayName: 'ALTER'
}]
}, {
name: 'Table',
displayName: 'Table',
permissions: [{
name: 'SELECT',
displayName: 'SELECT'
}, {
name: 'ALTER',
displayName: 'ALTER'
}, {
name: 'CONTROL',
displayName: 'CONTROL'
}, {
name: 'TAKE OWNERSHIP',
displayName: 'TAKE OWNERSHIP'
}]
}, {
name: 'View',
displayName: 'View',
permissions: [{
name: 'ALTER',
displayName: 'ALTER'
}, {
name: 'CONTROL',
displayName: 'CONTROL'
}, {
name: 'TAKE OWNERSHIP',
displayName: 'TAKE OWNERSHIP'
}]
}
]
const ServerLevelPermissions: ObjectManagement.SecurablePermissions[] = [
{
name: 'Server',
type: 'Server',
permissions: [
{
permission: 'CONNECT SQL',
grant: true,
grantor: 'sa',
withGrant: undefined
}, {
permission: 'VIEW ANY DATABASE',
grant: false,
grantor: 'sa',
withGrant: undefined
}
],
effectivePermissions: ['CONNECT SQL', 'VIEW ANY DATABASE']
}
];
const DatabaseLevelPermissions: ObjectManagement.SecurablePermissions[] = [
{
name: 'table1',
type: 'Table',
schema: 'dbo',
permissions: [
{
permission: 'SELECT',
grant: true,
grantor: '',
withGrant: undefined
}
],
effectivePermissions: ['SELECT']
}, {
name: 'view1',
type: 'View',
schema: 'Sales',
permissions: [
{
permission: 'ALTER',
grant: true,
grantor: '',
withGrant: undefined
}
],
effectivePermissions: ['ALTER']
}
];
export class TestObjectManagementService implements IObjectManagementService { export class TestObjectManagementService implements IObjectManagementService {
initializeView(contextId: string, objectType: ObjectManagement.NodeType, connectionUri: string, database: string, isNewObject: boolean, parentUrn: string, objectUrn: string): Thenable<ObjectManagement.ObjectViewInfo<ObjectManagement.SqlObject>> { initializeView(contextId: string, objectType: ObjectManagement.NodeType, connectionUri: string, database: string, isNewObject: boolean, parentUrn: string, objectUrn: string): Thenable<ObjectManagement.ObjectViewInfo<ObjectManagement.SqlObject>> {
let obj; let obj;
@@ -102,18 +222,18 @@ export class TestObjectManagementService implements IObjectManagementService {
return this.delayAndResolve(); return this.delayAndResolve();
} }
async search(contextId: string, objectTypes: ObjectManagement.NodeType[], searchText: string, schema: string): Promise<ObjectManagement.SearchResultItem[]> { async search(contextId: string, objectTypes: ObjectManagement.NodeType[], searchText?: string, schema?: string): Promise<ObjectManagement.SearchResultItem[]> {
const items: ObjectManagement.SearchResultItem[] = []; const items: ObjectManagement.SearchResultItem[] = [];
objectTypes.forEach(type => { objectTypes.forEach(type => {
items.push(...this.generateSearchResult(type, 15)); items.push(...this.generateSearchResult(type, schema, 15));
}); });
return this.delayAndResolve(items); return this.delayAndResolve(items);
} }
private generateSearchResult(objectType: ObjectManagement.NodeType, count: number): ObjectManagement.SearchResultItem[] { private generateSearchResult(objectType: ObjectManagement.NodeType, schema: string | undefined, count: number): ObjectManagement.SearchResultItem[] {
let items: ObjectManagement.SearchResultItem[] = []; let items: ObjectManagement.SearchResultItem[] = [];
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
items.push(<ObjectManagement.SearchResultItem>{ name: `${objectType} ${i}`, type: objectType }); items.push(<ObjectManagement.SearchResultItem>{ name: `${objectType} ${i}`, schema: schema, type: objectType });
} }
return items; return items;
} }
@@ -136,7 +256,8 @@ export class TestObjectManagementService implements IObjectManagementService {
serverRoles: ['public', 'bulkadmin'], serverRoles: ['public', 'bulkadmin'],
connectPermission: true, connectPermission: true,
isEnabled: true, isEnabled: true,
isLockedOut: false isLockedOut: false,
securablePermissions: []
}, },
authenticationTypes: [ObjectManagement.AuthenticationType.Sql, ObjectManagement.AuthenticationType.Windows], authenticationTypes: [ObjectManagement.AuthenticationType.Sql, ObjectManagement.AuthenticationType.Windows],
supportAdvancedOptions: true, supportAdvancedOptions: true,
@@ -144,7 +265,8 @@ export class TestObjectManagementService implements IObjectManagementService {
canEditLockedOutState: false, canEditLockedOutState: false,
languages: languages, languages: languages,
databases: databases, databases: databases,
serverRoles: serverRoles serverRoles: serverRoles,
supportedSecurableTypes: ServerLevelSecurableTypes
}; };
} else { } else {
login = <ObjectManagement.LoginViewInfo>{ login = <ObjectManagement.LoginViewInfo>{
@@ -160,7 +282,8 @@ export class TestObjectManagementService implements IObjectManagementService {
connectPermission: true, connectPermission: true,
isEnabled: true, isEnabled: true,
isLockedOut: false, isLockedOut: false,
password: '******************' password: '******************',
securablePermissions: ServerLevelPermissions
}, },
authenticationTypes: [ObjectManagement.AuthenticationType.Sql, ObjectManagement.AuthenticationType.Windows], authenticationTypes: [ObjectManagement.AuthenticationType.Sql, ObjectManagement.AuthenticationType.Windows],
supportAdvancedOptions: true, supportAdvancedOptions: true,
@@ -168,7 +291,8 @@ export class TestObjectManagementService implements IObjectManagementService {
canEditLockedOutState: false, canEditLockedOutState: false,
languages: languages, languages: languages,
databases: databases, databases: databases,
serverRoles: serverRoles serverRoles: serverRoles,
supportedSecurableTypes: ServerLevelSecurableTypes
}; };
} }
return login; return login;
@@ -192,7 +316,8 @@ export class TestObjectManagementService implements IObjectManagementService {
loginName: 'sa', loginName: 'sa',
ownedSchemas: [], ownedSchemas: [],
databaseRoles: [], databaseRoles: [],
password: '' password: '',
securablePermissions: []
}, },
languages: languages, languages: languages,
schemas: schemas, schemas: schemas,
@@ -203,7 +328,8 @@ export class TestObjectManagementService implements IObjectManagementService {
ObjectManagement.UserType.AADAuthentication, ObjectManagement.UserType.AADAuthentication,
ObjectManagement.UserType.SqlAuthentication, ObjectManagement.UserType.SqlAuthentication,
ObjectManagement.UserType.NoLoginAccess ObjectManagement.UserType.NoLoginAccess
] ],
supportedSecurableTypes: DatabaseLevelSecurableTypes
}; };
} else { } else {
viewInfo = { viewInfo = {
@@ -214,7 +340,8 @@ export class TestObjectManagementService implements IObjectManagementService {
defaultLanguage: '<default>', defaultLanguage: '<default>',
loginName: 'sa', loginName: 'sa',
ownedSchemas: ['dbo'], ownedSchemas: ['dbo'],
databaseRoles: ['dbmanager', 'bulkadmin'] databaseRoles: ['dbmanager', 'bulkadmin'],
securablePermissions: DatabaseLevelPermissions
}, },
languages: languages, languages: languages,
schemas: schemas, schemas: schemas,
@@ -225,7 +352,8 @@ export class TestObjectManagementService implements IObjectManagementService {
ObjectManagement.UserType.AADAuthentication, ObjectManagement.UserType.AADAuthentication,
ObjectManagement.UserType.SqlAuthentication, ObjectManagement.UserType.SqlAuthentication,
ObjectManagement.UserType.NoLoginAccess ObjectManagement.UserType.NoLoginAccess
] ],
supportedSecurableTypes: DatabaseLevelSecurableTypes
}; };
} }
return viewInfo; return viewInfo;
@@ -237,19 +365,23 @@ export class TestObjectManagementService implements IObjectManagementService {
name: '', name: '',
members: [], members: [],
owner: '', owner: '',
memberships: [] memberships: [],
securablePermissions: []
}, },
isFixedRole: false, isFixedRole: false,
serverRoles: ['ServerLevelServerRole 1', 'ServerLevelServerRole 2', 'ServerLevelServerRole 3', 'ServerLevelServerRole 4'], serverRoles: ['ServerLevelServerRole 1', 'ServerLevelServerRole 2', 'ServerLevelServerRole 3', 'ServerLevelServerRole 4'],
supportedSecurableTypes: ServerLevelSecurableTypes
} : <ObjectManagement.ServerRoleViewInfo>{ } : <ObjectManagement.ServerRoleViewInfo>{
objectInfo: { objectInfo: {
name: 'ServerLevelServerRole 1', name: 'ServerLevelServerRole 1',
members: ['ServerLevelLogin 1', 'ServerLevelServerRole 2'], members: ['ServerLevelLogin 1', 'ServerLevelServerRole 2'],
owner: 'ServerLevelLogin 2', owner: 'ServerLevelLogin 2',
memberships: ['ServerLevelServerRole 3', 'ServerLevelServerRole 4'] memberships: ['ServerLevelServerRole 3', 'ServerLevelServerRole 4'],
securablePermissions: ServerLevelPermissions
}, },
isFixedRole: false, isFixedRole: false,
serverRoles: ['ServerLevelServerRole 2', 'ServerLevelServerRole 3', 'ServerLevelServerRole 4'] serverRoles: ['ServerLevelServerRole 2', 'ServerLevelServerRole 3', 'ServerLevelServerRole 4'],
supportedSecurableTypes: ServerLevelSecurableTypes
}; };
} }
@@ -259,16 +391,20 @@ export class TestObjectManagementService implements IObjectManagementService {
name: '', name: '',
defaultSchema: 'dbo', defaultSchema: 'dbo',
ownedSchemas: [], ownedSchemas: [],
securablePermissions: []
}, },
schemas: ['dbo', 'sys', 'admin'] schemas: ['dbo', 'sys', 'admin'],
supportedSecurableTypes: []
} : <ObjectManagement.ApplicationRoleViewInfo>{ } : <ObjectManagement.ApplicationRoleViewInfo>{
objectInfo: { objectInfo: {
name: 'app role1', name: 'app role1',
password: '******************', password: '******************',
defaultSchema: 'dbo', defaultSchema: 'dbo',
ownedSchemas: ['dbo'], ownedSchemas: ['dbo'],
securablePermissions: DatabaseLevelPermissions
}, },
schemas: ['dbo', 'sys', 'admin'] schemas: ['dbo', 'sys', 'admin'],
supportedSecurableTypes: DatabaseLevelSecurableTypes
}; };
} }
@@ -278,17 +414,21 @@ export class TestObjectManagementService implements IObjectManagementService {
name: '', name: '',
owner: '', owner: '',
members: [], members: [],
ownedSchemas: [] ownedSchemas: [],
securablePermissions: []
}, },
schemas: ['dbo', 'sys', 'admin'] schemas: ['dbo', 'sys', 'admin'],
supportedSecurableTypes: DatabaseLevelSecurableTypes
} : <ObjectManagement.DatabaseRoleViewInfo>{ } : <ObjectManagement.DatabaseRoleViewInfo>{
objectInfo: { objectInfo: {
name: 'db role1', name: 'db role1',
owner: '', owner: '',
members: [], members: [],
ownedSchemas: ['dbo'] ownedSchemas: ['dbo'],
securablePermissions: DatabaseLevelPermissions
}, },
schemas: ['dbo', 'sys', 'admin'] schemas: ['dbo', 'sys', 'admin'],
supportedSecurableTypes: DatabaseLevelSecurableTypes
}; };
} }

View File

@@ -3,14 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase'; import { ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { IObjectManagementService, ObjectManagement } from 'mssql'; import { IObjectManagementService, ObjectManagement } from 'mssql';
import * as localizedConstants from '../localizedConstants'; import * as localizedConstants from '../localizedConstants';
import { AlterApplicationRoleDocUrl, CreateApplicationRoleDocUrl } from '../constants'; import { AlterApplicationRoleDocUrl, CreateApplicationRoleDocUrl } from '../constants';
import { isValidSQLPassword } from '../utils'; import { isValidSQLPassword } from '../utils';
import { DefaultMaxTableHeight } from '../../ui/dialogBase'; import { DefaultMaxTableRowCount } from '../../ui/dialogBase';
import { PrincipalDialogBase } from './principalDialogBase';
export class ApplicationRoleDialog extends ObjectManagementDialogBase<ObjectManagement.ApplicationRoleInfo, ObjectManagement.ApplicationRoleViewInfo> { export class ApplicationRoleDialog extends PrincipalDialogBase<ObjectManagement.ApplicationRoleInfo, ObjectManagement.ApplicationRoleViewInfo> {
// Sections // Sections
private generalSection: azdata.GroupContainer; private generalSection: azdata.GroupContainer;
private ownedSchemasSection: azdata.GroupContainer; private ownedSchemasSection: azdata.GroupContainer;
@@ -25,7 +26,7 @@ export class ApplicationRoleDialog extends ObjectManagementDialogBase<ObjectMana
private ownedSchemaTable: azdata.TableComponent; private ownedSchemaTable: azdata.TableComponent;
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) { constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options); super(objectManagementService, options, true, false);
} }
protected override postInitializeData(): void { protected override postInitializeData(): void {
@@ -51,10 +52,11 @@ export class ApplicationRoleDialog extends ObjectManagementDialogBase<ObjectMana
return errors; return errors;
} }
protected async initializeUI(): Promise<void> { protected override async initializeUI(): Promise<void> {
await super.initializeUI();
this.initializeGeneralSection(); this.initializeGeneralSection();
this.initializeOwnedSchemasSection(); this.initializeOwnedSchemasSection();
this.formContainer.addItems([this.generalSection, this.ownedSchemasSection]); this.formContainer.addItems([this.generalSection, this.ownedSchemasSection, this.securableSection], this.getSectionItemLayout());
} }
private initializeGeneralSection(): void { private initializeGeneralSection(): void {
@@ -84,7 +86,7 @@ export class ApplicationRoleDialog extends ObjectManagementDialogBase<ObjectMana
[localizedConstants.SchemaText], [localizedConstants.SchemaText],
this.viewInfo.schemas, this.viewInfo.schemas,
this.objectInfo.ownedSchemas, this.objectInfo.ownedSchemas,
DefaultMaxTableHeight, DefaultMaxTableRowCount,
(item) => { (item) => {
// It is not allowed to have unassigned schema. // It is not allowed to have unassigned schema.
return this.objectInfo.ownedSchemas.indexOf(item) === -1; return this.objectInfo.ownedSchemas.indexOf(item) === -1;

View File

@@ -3,14 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase'; import { ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { IObjectManagementService, ObjectManagement } from 'mssql'; import { IObjectManagementService, ObjectManagement } from 'mssql';
import * as localizedConstants from '../localizedConstants'; import * as localizedConstants from '../localizedConstants';
import { AlterDatabaseRoleDocUrl, CreateDatabaseRoleDocUrl } from '../constants'; import { AlterDatabaseRoleDocUrl, CreateDatabaseRoleDocUrl } from '../constants';
import { FindObjectDialog } from './findObjectDialog'; import { FindObjectDialog } from './findObjectDialog';
import { DefaultMaxTableHeight } from '../../ui/dialogBase'; import { DefaultMaxTableRowCount } from '../../ui/dialogBase';
import { PrincipalDialogBase } from './principalDialogBase';
export class DatabaseRoleDialog extends ObjectManagementDialogBase<ObjectManagement.DatabaseRoleInfo, ObjectManagement.DatabaseRoleViewInfo> { export class DatabaseRoleDialog extends PrincipalDialogBase<ObjectManagement.DatabaseRoleInfo, ObjectManagement.DatabaseRoleViewInfo> {
// Sections // Sections
private generalSection: azdata.GroupContainer; private generalSection: azdata.GroupContainer;
private ownedSchemasSection: azdata.GroupContainer; private ownedSchemasSection: azdata.GroupContainer;
@@ -27,18 +28,19 @@ export class DatabaseRoleDialog extends ObjectManagementDialogBase<ObjectManagem
private memberTable: azdata.TableComponent; private memberTable: azdata.TableComponent;
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) { constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options); super(objectManagementService, options, true, false);
} }
protected override get helpUrl(): string { protected override get helpUrl(): string {
return this.options.isNewObject ? CreateDatabaseRoleDocUrl : AlterDatabaseRoleDocUrl; return this.options.isNewObject ? CreateDatabaseRoleDocUrl : AlterDatabaseRoleDocUrl;
} }
protected async initializeUI(): Promise<void> { protected override async initializeUI(): Promise<void> {
await super.initializeUI();
this.initializeGeneralSection(); this.initializeGeneralSection();
this.initializeOwnedSchemasSection(); this.initializeOwnedSchemasSection();
this.initializeMemberSection(); this.initializeMemberSection();
this.formContainer.addItems([this.generalSection, this.ownedSchemasSection, this.memberSection]); this.formContainer.addItems([this.generalSection, this.ownedSchemasSection, this.memberSection, this.securableSection], this.getSectionItemLayout());
} }
private initializeGeneralSection(): void { private initializeGeneralSection(): void {
@@ -53,9 +55,11 @@ export class DatabaseRoleDialog extends ObjectManagementDialogBase<ObjectManagem
const browseOwnerButton = this.createButton(localizedConstants.BrowseText, localizedConstants.BrowseOwnerButtonAriaLabel, async () => { const browseOwnerButton = this.createButton(localizedConstants.BrowseText, localizedConstants.BrowseOwnerButtonAriaLabel, async () => {
const dialog = new FindObjectDialog(this.objectManagementService, { const dialog = new FindObjectDialog(this.objectManagementService, {
objectTypes: [ObjectManagement.NodeType.ApplicationRole, ObjectManagement.NodeType.DatabaseRole, ObjectManagement.NodeType.User], objectTypes: [ObjectManagement.NodeType.ApplicationRole, ObjectManagement.NodeType.DatabaseRole, ObjectManagement.NodeType.User],
selectAllObjectTypes: true,
multiSelect: false, multiSelect: false,
contextId: this.contextId, contextId: this.contextId,
title: localizedConstants.SelectDatabaseRoleOwnerDialogTitle title: localizedConstants.SelectDatabaseRoleOwnerDialogTitle,
showSchemaColumn: false
}); });
await dialog.open(); await dialog.open();
const result = await dialog.waitForClose(); const result = await dialog.waitForClose();
@@ -70,48 +74,45 @@ export class DatabaseRoleDialog extends ObjectManagementDialogBase<ObjectManagem
} }
private initializeMemberSection(): void { private initializeMemberSection(): void {
this.memberTable = this.createTable(localizedConstants.MemberSectionHeader, [ this.memberTable = this.createTable(localizedConstants.MemberSectionHeader, [localizedConstants.NameText], this.objectInfo.members.map(m => [m]));
{
type: azdata.ColumnType.text,
value: localizedConstants.NameText
}
], this.objectInfo.members.map(m => [m]));
const buttonContainer = this.addButtonsForTable(this.memberTable, localizedConstants.AddMemberAriaLabel, localizedConstants.RemoveMemberAriaLabel, const buttonContainer = this.addButtonsForTable(this.memberTable, localizedConstants.AddMemberAriaLabel, localizedConstants.RemoveMemberAriaLabel,
async () => { async () => {
const dialog = new FindObjectDialog(this.objectManagementService, { const dialog = new FindObjectDialog(this.objectManagementService, {
objectTypes: [ObjectManagement.NodeType.DatabaseRole, ObjectManagement.NodeType.User], objectTypes: [ObjectManagement.NodeType.DatabaseRole, ObjectManagement.NodeType.User],
selectAllObjectTypes: true,
multiSelect: true, multiSelect: true,
contextId: this.contextId, contextId: this.contextId,
title: localizedConstants.SelectDatabaseRoleMemberDialogTitle title: localizedConstants.SelectDatabaseRoleMemberDialogTitle,
showSchemaColumn: false
}); });
await dialog.open(); await dialog.open();
const result = await dialog.waitForClose(); const result = await dialog.waitForClose();
this.addMembers(result.selectedObjects.map(r => r.name)); await this.addMembers(result.selectedObjects.map(r => r.name));
}, },
async () => { async () => {
if (this.memberTable.selectedRows.length === 1) { if (this.memberTable.selectedRows.length === 1) {
this.removeMember(this.memberTable.selectedRows[0]); await this.removeMember(this.memberTable.selectedRows[0]);
} }
}); });
this.memberSection = this.createGroup(localizedConstants.MemberSectionHeader, [this.memberTable, buttonContainer]); this.memberSection = this.createGroup(localizedConstants.MemberSectionHeader, [this.memberTable, buttonContainer]);
} }
private addMembers(names: string[]): void { private async addMembers(names: string[]): Promise<void> {
names.forEach(n => { names.forEach(n => {
if (this.objectInfo.members.indexOf(n) === -1) { if (this.objectInfo.members.indexOf(n) === -1) {
this.objectInfo.members.push(n); this.objectInfo.members.push(n);
} }
}); });
this.updateMembersTable(); await this.updateMembersTable();
} }
private removeMember(idx: number): void { private async removeMember(idx: number): Promise<void> {
this.objectInfo.members.splice(idx, 1); this.objectInfo.members.splice(idx, 1);
this.updateMembersTable(); await this.updateMembersTable();
} }
private updateMembersTable(): void { private async updateMembersTable(): Promise<void> {
this.setTableData(this.memberTable, this.objectInfo.members.map(m => [m])); await this.setTableData(this.memberTable, this.objectInfo.members.map(m => [m]));
this.onFormFieldChange(); this.onFormFieldChange();
} }
@@ -120,7 +121,7 @@ export class DatabaseRoleDialog extends ObjectManagementDialogBase<ObjectManagem
[localizedConstants.SchemaText], [localizedConstants.SchemaText],
this.viewInfo.schemas, this.viewInfo.schemas,
this.objectInfo.ownedSchemas, this.objectInfo.ownedSchemas,
DefaultMaxTableHeight, DefaultMaxTableRowCount,
(item) => { (item) => {
// It is not allowed to have unassigned schema. // It is not allowed to have unassigned schema.
return this.objectInfo.ownedSchemas.indexOf(item) === -1; return this.objectInfo.ownedSchemas.indexOf(item) === -1;

View File

@@ -5,15 +5,19 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as mssql from 'mssql'; import * as mssql from 'mssql';
import { DefaultTableListItemEnabledStateGetter, DefaultMaxTableHeight, DialogBase, TableListItemComparer, TableListItemValueGetter } from '../../ui/dialogBase'; import { DefaultTableListItemEnabledStateGetter, DefaultMaxTableRowCount, DialogBase, TableListItemComparer } from '../../ui/dialogBase';
import * as localizedConstants from '../localizedConstants'; import * as localizedConstants from '../localizedConstants';
import { getErrorMessage } from '../../utils'; import { getErrorMessage } from '../../utils';
type ObjectType = string | { name: string, displayName: string };
export interface FindObjectDialogOptions { export interface FindObjectDialogOptions {
objectTypes: mssql.ObjectManagement.NodeType[]; objectTypes: ObjectType[];
selectAllObjectTypes: boolean;
multiSelect: boolean; multiSelect: boolean;
contextId: string; contextId: string;
title: string; title: string;
showSchemaColumn?: boolean;
} }
export interface FindObjectDialogResult { export interface FindObjectDialogResult {
@@ -22,15 +26,10 @@ export interface FindObjectDialogResult {
const ObjectComparer: TableListItemComparer<mssql.ObjectManagement.SearchResultItem> = const ObjectComparer: TableListItemComparer<mssql.ObjectManagement.SearchResultItem> =
(item1, item2) => { (item1, item2) => {
return item1.name === item2.name && item1.type === item2.type; return item1.name === item2.name && item1.type === item2.type && item1.schema === item2.schema;
}; };
const ObjectRowValueGetter: TableListItemValueGetter<mssql.ObjectManagement.SearchResultItem> = const ObjectsTableMaxRowCount = 20;
(item) => {
return [item.name, localizedConstants.getNodeTypeDisplayName(item.type, true)];
};
const ObjectsTableMaxHeight = 700;
export class FindObjectDialog extends DialogBase<FindObjectDialogResult> { export class FindObjectDialog extends DialogBase<FindObjectDialogResult> {
private objectTypesTable: azdata.TableComponent; private objectTypesTable: azdata.TableComponent;
@@ -38,7 +37,7 @@ export class FindObjectDialog extends DialogBase<FindObjectDialogResult> {
private objectsTable: azdata.TableComponent; private objectsTable: azdata.TableComponent;
private objectsLoadingComponent: azdata.LoadingComponent; private objectsLoadingComponent: azdata.LoadingComponent;
private result: FindObjectDialogResult; private result: FindObjectDialogResult;
private selectedObjectTypes: string[] = []; private selectedObjectTypes: ObjectType[] = [];
private allObjects: mssql.ObjectManagement.SearchResultItem[] = []; private allObjects: mssql.ObjectManagement.SearchResultItem[] = [];
constructor(private readonly objectManagementService: mssql.IObjectManagementService, private readonly options: FindObjectDialogOptions) { constructor(private readonly objectManagementService: mssql.IObjectManagementService, private readonly options: FindObjectDialogOptions) {
@@ -47,40 +46,52 @@ export class FindObjectDialog extends DialogBase<FindObjectDialogResult> {
this.result = { this.result = {
selectedObjects: [] selectedObjects: []
}; };
this.selectedObjectTypes = [...options.objectTypes]; this.selectedObjectTypes = options.selectAllObjectTypes ? [...options.objectTypes] : [];
}
private getObjectTypeName(objectType: ObjectType): string {
return typeof objectType === 'string' ? objectType : objectType.name;
}
private getObjectTypeDisplayName(objectType: ObjectType): string {
return typeof objectType === 'string' ? localizedConstants.getNodeTypeDisplayName(objectType, true) : objectType.displayName;
} }
protected override async initialize(): Promise<void> { protected override async initialize(): Promise<void> {
this.dialogObject.okButton.enabled = false; this.dialogObject.okButton.enabled = false;
this.objectTypesTable = this.createTableList<string>(localizedConstants.ObjectTypeText, this.objectTypesTable = this.createTableList<ObjectType>(localizedConstants.ObjectTypeText,
[localizedConstants.ObjectTypeText], [localizedConstants.ObjectTypeText],
this.options.objectTypes, this.options.objectTypes,
this.selectedObjectTypes, this.selectedObjectTypes,
DefaultMaxTableHeight, DefaultMaxTableRowCount,
DefaultTableListItemEnabledStateGetter, (item) => { DefaultTableListItemEnabledStateGetter, (item) => {
return [localizedConstants.getNodeTypeDisplayName(item, true)]; return [this.getObjectTypeDisplayName(item)];
}, (item1, item2) => {
return this.getObjectTypeName(item1) === this.getObjectTypeName(item2);
}); });
this.findButton = this.createButton(localizedConstants.FindText, localizedConstants.FindText, async () => { this.findButton = this.createButton(localizedConstants.FindText, localizedConstants.FindText, async () => {
await this.onFindObjectButtonClick(); await this.onFindObjectButtonClick();
}); }, this.options.selectAllObjectTypes);
const buttonContainer = this.createButtonContainer([this.findButton]); const buttonContainer = this.createButtonContainer([this.findButton]);
const objectTypeSection = this.createGroup(localizedConstants.ObjectTypeText, [this.objectTypesTable, buttonContainer]); const objectTypeSection = this.createGroup(localizedConstants.ObjectTypeText, [this.objectTypesTable, buttonContainer]);
const columns = [localizedConstants.NameText, localizedConstants.ObjectTypeText];
if (this.options.showSchemaColumn) {
columns.splice(1, 0, localizedConstants.SchemaText);
}
if (this.options.multiSelect) { if (this.options.multiSelect) {
this.objectsTable = this.createTableList<mssql.ObjectManagement.SearchResultItem>(localizedConstants.ObjectsText, this.objectsTable = this.createTableList<mssql.ObjectManagement.SearchResultItem>(localizedConstants.ObjectsText,
[localizedConstants.NameText, localizedConstants.ObjectTypeText], columns,
this.allObjects, this.allObjects,
this.result.selectedObjects, this.result.selectedObjects,
ObjectsTableMaxHeight, ObjectsTableMaxRowCount,
DefaultTableListItemEnabledStateGetter, DefaultTableListItemEnabledStateGetter,
ObjectRowValueGetter, (item) => {
return this.getObjectRowValue(item);
},
ObjectComparer); ObjectComparer);
} else { } else {
this.objectsTable = this.createTable(localizedConstants.ObjectsText, [{ this.objectsTable = this.createTable(localizedConstants.ObjectsText, columns, [], ObjectsTableMaxRowCount);
value: localizedConstants.NameText,
}, {
value: localizedConstants.ObjectTypeText
}], []);
this.disposables.push(this.objectsTable.onRowSelected(async () => { this.disposables.push(this.objectsTable.onRowSelected(async () => {
if (this.objectsTable.selectedRows.length > 0) { if (this.objectsTable.selectedRows.length > 0) {
this.result.selectedObjects = [this.allObjects[this.objectsTable.selectedRows[0]]]; this.result.selectedObjects = [this.allObjects[this.objectsTable.selectedRows[0]]];
@@ -95,7 +106,7 @@ export class FindObjectDialog extends DialogBase<FindObjectDialogResult> {
}).component(); }).component();
const objectsSection = this.createGroup(localizedConstants.ObjectsText, [this.objectsLoadingComponent]); const objectsSection = this.createGroup(localizedConstants.ObjectsText, [this.objectsLoadingComponent]);
this.formContainer.addItems([objectTypeSection, objectsSection]); this.formContainer.addItems([objectTypeSection, objectsSection], this.getSectionItemLayout());
} }
protected override get dialogResult(): FindObjectDialogResult | undefined { protected override get dialogResult(): FindObjectDialogResult | undefined {
@@ -107,16 +118,18 @@ export class FindObjectDialog extends DialogBase<FindObjectDialogResult> {
this.objectsLoadingComponent.loading = true; this.objectsLoadingComponent.loading = true;
this.findButton.enabled = false; this.findButton.enabled = false;
try { try {
const results = await this.objectManagementService.search(this.options.contextId, <mssql.ObjectManagement.NodeType[]>this.selectedObjectTypes); const results = await this.objectManagementService.search(this.options.contextId, this.selectedObjectTypes.map(item => this.getObjectTypeName(item)));
this.allObjects.splice(0, this.allObjects.length, ...results); this.allObjects.splice(0, this.allObjects.length, ...results);
let data; let data;
if (this.options.multiSelect) { if (this.options.multiSelect) {
data = this.getDataForTableList(this.allObjects, this.result.selectedObjects, DefaultTableListItemEnabledStateGetter, ObjectRowValueGetter, ObjectComparer); data = this.getDataForTableList(this.allObjects, this.result.selectedObjects, DefaultTableListItemEnabledStateGetter, (item) => {
return this.getObjectRowValue(item);
}, ObjectComparer);
} }
else { else {
data = this.allObjects.map(item => ObjectRowValueGetter(item)); data = this.allObjects.map(item => { return this.getObjectRowValue(item); });
} }
this.setTableData(this.objectsTable, data, ObjectsTableMaxHeight); await this.setTableData(this.objectsTable, data, ObjectsTableMaxRowCount);
this.objectsLoadingComponent.loadingCompletedText = localizedConstants.LoadingObjectsCompletedText(results.length); this.objectsLoadingComponent.loadingCompletedText = localizedConstants.LoadingObjectsCompletedText(results.length);
} catch (err) { } catch (err) {
this.dialogObject.message = { this.dialogObject.message = {
@@ -132,4 +145,12 @@ export class FindObjectDialog extends DialogBase<FindObjectDialogResult> {
this.findButton.enabled = this.selectedObjectTypes.length > 0; this.findButton.enabled = this.selectedObjectTypes.length > 0;
this.dialogObject.okButton.enabled = this.result.selectedObjects.length > 0; this.dialogObject.okButton.enabled = this.result.selectedObjects.length > 0;
} }
private getObjectRowValue(item: mssql.ObjectManagement.SearchResultItem): string[] {
const row = [item.name, this.getObjectTypeName(item.type)];
if (this.options.showSchemaColumn) {
row.splice(1, 0, item.schema);
}
return row;
}
} }

View File

@@ -4,15 +4,16 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase'; import { ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { IObjectManagementService, ObjectManagement } from 'mssql'; import { IObjectManagementService, ObjectManagement } from 'mssql';
import * as objectManagementLoc from '../localizedConstants'; import * as objectManagementLoc from '../localizedConstants';
import * as uiLoc from '../../ui/localizedConstants'; import * as uiLoc from '../../ui/localizedConstants';
import { AlterLoginDocUrl, CreateLoginDocUrl, PublicServerRoleName } from '../constants'; import { AlterLoginDocUrl, CreateLoginDocUrl, PublicServerRoleName } from '../constants';
import { isValidSQLPassword } from '../utils'; import { isValidSQLPassword } from '../utils';
import { DefaultMaxTableHeight } from '../../ui/dialogBase'; import { DefaultMaxTableRowCount } from '../../ui/dialogBase';
import { PrincipalDialogBase } from './principalDialogBase';
export class LoginDialog extends ObjectManagementDialogBase<ObjectManagement.Login, ObjectManagement.LoginViewInfo> { export class LoginDialog extends PrincipalDialogBase<ObjectManagement.Login, ObjectManagement.LoginViewInfo> {
private generalSection: azdata.GroupContainer; private generalSection: azdata.GroupContainer;
private sqlAuthSection: azdata.GroupContainer; private sqlAuthSection: azdata.GroupContainer;
private serverRoleSection: azdata.GroupContainer; private serverRoleSection: azdata.GroupContainer;
@@ -34,7 +35,7 @@ export class LoginDialog extends ObjectManagementDialogBase<ObjectManagement.Log
private lockedOutCheckbox: azdata.CheckBoxComponent; private lockedOutCheckbox: azdata.CheckBoxComponent;
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) { constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options); super(objectManagementService, options, false);
} }
protected override get helpUrl(): string { protected override get helpUrl(): string {
@@ -82,7 +83,8 @@ export class LoginDialog extends ObjectManagementDialogBase<ObjectManagement.Log
this.objectInfo.password = this.objectInfo.password ?? ''; this.objectInfo.password = this.objectInfo.password ?? '';
} }
protected async initializeUI(): Promise<void> { protected override async initializeUI(): Promise<void> {
await super.initializeUI();
const sections: azdata.Component[] = []; const sections: azdata.Component[] = [];
this.initializeGeneralSection(); this.initializeGeneralSection();
sections.push(this.generalSection); sections.push(this.generalSection);
@@ -94,12 +96,13 @@ export class LoginDialog extends ObjectManagementDialogBase<ObjectManagement.Log
this.initializeServerRolesSection(); this.initializeServerRolesSection();
sections.push(this.serverRoleSection); sections.push(this.serverRoleSection);
sections.push(this.securableSection);
if (this.viewInfo.supportAdvancedOptions) { if (this.viewInfo.supportAdvancedOptions) {
this.initializeAdvancedSection(); this.initializeAdvancedSection();
sections.push(this.advancedSection); sections.push(this.advancedSection);
} }
this.formContainer.addItems(sections); this.formContainer.addItems(sections, this.getSectionItemLayout());
} }
private initializeGeneralSection(): void { private initializeGeneralSection(): void {
@@ -203,7 +206,7 @@ export class LoginDialog extends ObjectManagementDialogBase<ObjectManagement.Log
items.push(defaultDatabaseContainer, defaultLanguageContainer, this.connectPermissionCheckbox); items.push(defaultDatabaseContainer, defaultLanguageContainer, this.connectPermissionCheckbox);
} }
this.advancedSection = this.createGroup(objectManagementLoc.AdvancedSectionHeader, items); this.advancedSection = this.createGroup(objectManagementLoc.AdvancedSectionHeader, items, true, true);
} }
private initializeServerRolesSection(): void { private initializeServerRolesSection(): void {
@@ -211,7 +214,7 @@ export class LoginDialog extends ObjectManagementDialogBase<ObjectManagement.Log
[objectManagementLoc.ServerRoleTypeDisplayNameInTitle], [objectManagementLoc.ServerRoleTypeDisplayNameInTitle],
this.viewInfo.serverRoles, this.viewInfo.serverRoles,
this.objectInfo.serverRoles, this.objectInfo.serverRoles,
DefaultMaxTableHeight, DefaultMaxTableRowCount,
(item) => { (item) => {
return item !== PublicServerRoleName return item !== PublicServerRoleName
}); });
@@ -220,7 +223,7 @@ export class LoginDialog extends ObjectManagementDialogBase<ObjectManagement.Log
private setViewByAuthenticationType(): void { private setViewByAuthenticationType(): void {
if (this.authTypeDropdown.value === objectManagementLoc.SQLAuthenticationTypeDisplayText) { if (this.authTypeDropdown.value === objectManagementLoc.SQLAuthenticationTypeDisplayText) {
this.addItem(this.formContainer, this.sqlAuthSection, 1); this.addItem(this.formContainer, this.sqlAuthSection, this.getSectionItemLayout(), 1);
} else if (this.authTypeDropdown.value !== objectManagementLoc.SQLAuthenticationTypeDisplayText) { } else if (this.authTypeDropdown.value !== objectManagementLoc.SQLAuthenticationTypeDisplayText) {
this.removeItem(this.formContainer, this.sqlAuthSection); this.removeItem(this.formContainer, this.sqlAuthSection);
} }

View File

@@ -0,0 +1,228 @@
/*---------------------------------------------------------------------------------------------
* 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 mssql from 'mssql';
import * as localizedConstants from '../localizedConstants';
import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { FindObjectDialog } from './findObjectDialog';
import { deepClone } from '../../util/objects';
import { DefaultTableWidth, getTableHeight } from '../../ui/dialogBase';
const GrantColumnIndex = 2;
const WithGrantColumnIndex = 3;
const DenyColumnIndex = 4;
/**
* Base class for security principal dialogs such as user, role, etc.
*/
export abstract class PrincipalDialogBase<ObjectInfoType extends mssql.ObjectManagement.SecurityPrincipalObject, ViewInfoType extends mssql.ObjectManagement.SecurityPrincipalViewInfo<ObjectInfoType>> extends ObjectManagementDialogBase<ObjectInfoType, ViewInfoType> {
protected securableTable: azdata.TableComponent;
protected permissionTable: azdata.TableComponent;
protected effectivePermissionTable: azdata.TableComponent;
protected securableSection: azdata.GroupContainer;
protected explicitPermissionTableLabel: azdata.TextComponent;
protected effectivePermissionTableLabel: azdata.TextComponent;
private securablePermissions: mssql.ObjectManagement.SecurablePermissions[] = [];
constructor(objectManagementService: mssql.IObjectManagementService, options: ObjectManagementDialogOptions, private readonly showSchemaColumn: boolean, private readonly supportEffectivePermissions: boolean = true) {
super(objectManagementService, options);
}
protected override async initializeUI(): Promise<void> {
this.securablePermissions = deepClone(this.objectInfo.securablePermissions);
this.initializeSecurableSection();
}
private initializeSecurableSection(): void {
const items: azdata.Component[] = [];
const securableTableColumns = [localizedConstants.NameText, localizedConstants.ObjectTypeText];
if (this.showSchemaColumn) {
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,
() => this.onAddSecurableButtonClicked(), () => this.onRemoveSecurableButtonClicked());
this.disposables.push(this.securableTable.onRowSelected(async () => {
await this.updatePermissionsTable();
}));
this.explicitPermissionTableLabel = this.modelView.modelBuilder.text().withProps({ value: localizedConstants.ExplicitPermissionsTableLabel }).component();
this.permissionTable = this.modelView.modelBuilder.table().withProps({
ariaLabel: localizedConstants.ExplicitPermissionsTableLabel,
columns:
[{
type: azdata.ColumnType.text,
value: localizedConstants.PermissionColumnHeader
}, {
type: azdata.ColumnType.text,
value: localizedConstants.GrantorColumnHeader
}, {
type: azdata.ColumnType.checkBox,
value: localizedConstants.GrantColumnHeader
}, {
type: azdata.ColumnType.checkBox,
value: localizedConstants.WithGrantColumnHeader
}, {
type: azdata.ColumnType.checkBox,
value: localizedConstants.DenyColumnHeader
}],
data: [],
height: getTableHeight(0),
width: DefaultTableWidth
}).component();
this.disposables.push(this.permissionTable.onCellAction(async (arg: azdata.ICheckboxCellActionEventArgs) => {
const permissionName = this.permissionTable.data[arg.row][0];
const securable = this.securablePermissions[this.securableTable.selectedRows[0]];
let permission: mssql.ObjectManagement.SecurablePermissionItem = securable.permissions.find(securablePermission => securablePermission.permission === permissionName);
if (!permission) {
permission = {
permission: permissionName,
grantor: ''
};
securable.permissions.push(permission);
}
if (arg.column === GrantColumnIndex) {
permission.grant = arg.checked ? true : undefined;
if (!arg.checked) {
permission.withGrant = undefined;
}
} else if (arg.column === WithGrantColumnIndex) {
permission.withGrant = arg.checked ? true : undefined;
if (arg.checked) {
permission.grant = true;
}
} else if (arg.column === DenyColumnIndex) {
permission.grant = arg.checked ? false : undefined;
if (arg.checked) {
permission.withGrant = undefined;
}
}
await this.updatePermissionsTable();
this.updateSecurablePermissions();
// Restore the focus to previously selected cell.
this.permissionTable.setActiveCell(arg.row, arg.column);
}));
items.push(this.securableTable, buttonContainer, this.explicitPermissionTableLabel, this.permissionTable);
if (this.showEffectivePermissions) {
this.effectivePermissionTableLabel = this.modelView.modelBuilder.text().withProps({ value: localizedConstants.EffectivePermissionsTableLabel }).component();
this.effectivePermissionTable = this.createTable(localizedConstants.EffectivePermissionsTableLabel, [localizedConstants.PermissionColumnHeader], []);
items.push(this.effectivePermissionTableLabel, this.effectivePermissionTable);
}
this.securableSection = this.createGroup(localizedConstants.SecurablesText, items, true, true);
}
private async onAddSecurableButtonClicked(): Promise<void> {
const dialog = new FindObjectDialog(this.objectManagementService, {
objectTypes: this.viewInfo.supportedSecurableTypes,
selectAllObjectTypes: false,
multiSelect: true,
contextId: this.contextId,
title: localizedConstants.SelectSecurablesDialogTitle,
showSchemaColumn: this.showSchemaColumn
});
await dialog.open();
const result = await dialog.waitForClose();
if (result && result.selectedObjects.length > 0) {
result.selectedObjects.forEach(obj => {
if (this.securablePermissions.find(securable => securable.type === obj.type && securable.name === obj.name && securable.schema === obj.schema)) {
return;
}
const securableTypeMetadata = this.viewInfo.supportedSecurableTypes.find(securableType => securableType.name === obj.type);
this.securablePermissions.push({
name: obj.name,
schema: obj.schema,
type: obj.type,
permissions: securableTypeMetadata.permissions.map(permission => {
return {
permission: permission.name,
grantor: '',
grant: undefined,
withGrant: undefined
};
}),
effectivePermissions: []
});
});
const data = this.getSecurableTableData();
await this.setTableData(this.securableTable, data);
}
}
private async onRemoveSecurableButtonClicked(): Promise<void> {
if (this.securableTable.selectedRows.length === 1) {
this.securablePermissions.splice(this.securableTable.selectedRows[0], 1);
const data = this.getSecurableTableData();
await this.setTableData(this.securableTable, data);
this.updateSecurablePermissions();
}
}
private getSecurableTableData(): string[][] {
return this.securablePermissions.map(securable => {
const row = [securable.name, this.getSecurableTypeDisplayName(securable.type)];
if (this.showSchemaColumn) {
row.splice(1, 0, securable.schema);
}
return row;
});
}
private async updatePermissionsTable(): Promise<void> {
let permissionsTableData: any[][] = [];
let effectivePermissionsTableData: any[][] = [];
let explicitPermissionsLabel = localizedConstants.ExplicitPermissionsTableLabel;
let effectivePermissionsLabel = localizedConstants.EffectivePermissionsTableLabel;
if (this.securableTable.selectedRows.length === 1) {
const securable = this.securablePermissions[this.securableTable.selectedRows[0]];
if (securable) {
const securableDisplayName = securable.schema ? `${securable.schema}.${securable.name}` : securable.name;
explicitPermissionsLabel = localizedConstants.ExplicitPermissionsTableLabelSelected(securableDisplayName);
effectivePermissionsLabel = localizedConstants.EffectivePermissionsTableLabelSelected(securableDisplayName);
const securableTypeMetadata = this.viewInfo.supportedSecurableTypes.find(securableType => securableType.name === securable.type);
permissionsTableData = securable.permissions.map(permission => {
return [permission.permission, permission.grantor, { checked: permission.grant === true }, { checked: permission.withGrant === true }, { checked: permission.grant === false }];
});
permissionsTableData = securableTypeMetadata.permissions.map(permissionMetadata => {
const permission = securable.permissions.find(securablePermission => securablePermission.permission === permissionMetadata.name);
return [
permissionMetadata.name,
permission?.grantor ?? '',
{ checked: permission?.grant === true },
{ checked: permission?.withGrant === true },
{ checked: permission?.grant === false }];
});
effectivePermissionsTableData = securable.effectivePermissions.map(permission => [permission]);
}
}
this.explicitPermissionTableLabel.value = explicitPermissionsLabel;
await this.setTableData(this.permissionTable, permissionsTableData);
if (this.showEffectivePermissions) {
this.effectivePermissionTableLabel.value = effectivePermissionsLabel;
await this.setTableData(this.effectivePermissionTable, effectivePermissionsTableData);
}
}
private updateSecurablePermissions(): void {
// Only save securable permissions that have grant or deny value.
this.objectInfo.securablePermissions = deepClone(this.securablePermissions.filter((securablePermissions) => {
return securablePermissions.permissions.some(permission => permission.grant !== undefined);
}));
this.objectInfo.securablePermissions.forEach(securablePermissions => {
securablePermissions.permissions = securablePermissions.permissions.filter(permission => permission.grant !== undefined);
});
this.onFormFieldChange();
}
private getSecurableTypeDisplayName(securableType: string): string {
const securableTypeMetadata = this.viewInfo.supportedSecurableTypes.find(securableTypeMetadata => securableTypeMetadata.name === securableType);
return securableTypeMetadata ? securableTypeMetadata.displayName : securableType;
}
private get showEffectivePermissions(): boolean {
return !this.options.isNewObject && this.supportEffectivePermissions;
}
}

View File

@@ -3,13 +3,14 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase'; import { ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { IObjectManagementService, ObjectManagement } from 'mssql'; import { IObjectManagementService, ObjectManagement } from 'mssql';
import * as localizedConstants from '../localizedConstants'; import * as localizedConstants from '../localizedConstants';
import { AlterServerRoleDocUrl, CreateServerRoleDocUrl } from '../constants'; import { AlterServerRoleDocUrl, CreateServerRoleDocUrl } from '../constants';
import { FindObjectDialog } from './findObjectDialog'; import { FindObjectDialog } from './findObjectDialog';
import { PrincipalDialogBase } from './principalDialogBase';
export class ServerRoleDialog extends ObjectManagementDialogBase<ObjectManagement.ServerRoleInfo, ObjectManagement.ServerRoleViewInfo> { export class ServerRoleDialog extends PrincipalDialogBase<ObjectManagement.ServerRoleInfo, ObjectManagement.ServerRoleViewInfo> {
// Sections // Sections
private generalSection: azdata.GroupContainer; private generalSection: azdata.GroupContainer;
private membershipSection: azdata.GroupContainer; private membershipSection: azdata.GroupContainer;
@@ -27,22 +28,24 @@ export class ServerRoleDialog extends ObjectManagementDialogBase<ObjectManagemen
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) { constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options); super(objectManagementService, options, false, false);
} }
protected override get helpUrl(): string { protected override get helpUrl(): string {
return this.options.isNewObject ? CreateServerRoleDocUrl : AlterServerRoleDocUrl; return this.options.isNewObject ? CreateServerRoleDocUrl : AlterServerRoleDocUrl;
} }
protected async initializeUI(): Promise<void> { protected override async initializeUI(): Promise<void> {
await super.initializeUI();
this.initializeGeneralSection(); this.initializeGeneralSection();
this.initializeMemberSection(); this.initializeMemberSection();
const sections: azdata.Component[] = [this.generalSection, this.memberSection]; const sections: azdata.Component[] = [this.generalSection, this.memberSection];
if (!this.viewInfo.isFixedRole) { if (!this.viewInfo.isFixedRole) {
this.initializeMembershipSection(); this.initializeMembershipSection();
sections.push(this.membershipSection); sections.push(this.membershipSection);
sections.push(this.securableSection);
} }
this.formContainer.addItems(sections); this.formContainer.addItems(sections, this.getSectionItemLayout());
} }
private initializeGeneralSection(): void { private initializeGeneralSection(): void {
@@ -57,6 +60,7 @@ export class ServerRoleDialog extends ObjectManagementDialogBase<ObjectManagemen
const browseOwnerButton = this.createButton(localizedConstants.BrowseText, localizedConstants.BrowseOwnerButtonAriaLabel, async () => { const browseOwnerButton = this.createButton(localizedConstants.BrowseText, localizedConstants.BrowseOwnerButtonAriaLabel, async () => {
const dialog = new FindObjectDialog(this.objectManagementService, { const dialog = new FindObjectDialog(this.objectManagementService, {
objectTypes: [ObjectManagement.NodeType.ServerLevelLogin, ObjectManagement.NodeType.ServerLevelServerRole], objectTypes: [ObjectManagement.NodeType.ServerLevelLogin, ObjectManagement.NodeType.ServerLevelServerRole],
selectAllObjectTypes: true,
multiSelect: false, multiSelect: false,
contextId: this.contextId, contextId: this.contextId,
title: localizedConstants.SelectServerRoleOwnerDialogTitle title: localizedConstants.SelectServerRoleOwnerDialogTitle
@@ -76,48 +80,44 @@ export class ServerRoleDialog extends ObjectManagementDialogBase<ObjectManagemen
} }
private initializeMemberSection(): void { private initializeMemberSection(): void {
this.memberTable = this.createTable(localizedConstants.MemberSectionHeader, [ this.memberTable = this.createTable(localizedConstants.MemberSectionHeader, [localizedConstants.NameText], this.objectInfo.members.map(m => [m]));
{
type: azdata.ColumnType.text,
value: localizedConstants.NameText
}
], this.objectInfo.members.map(m => [m]));
const buttonContainer = this.addButtonsForTable(this.memberTable, localizedConstants.AddMemberAriaLabel, localizedConstants.RemoveMemberAriaLabel, const buttonContainer = this.addButtonsForTable(this.memberTable, localizedConstants.AddMemberAriaLabel, localizedConstants.RemoveMemberAriaLabel,
async () => { async () => {
const dialog = new FindObjectDialog(this.objectManagementService, { const dialog = new FindObjectDialog(this.objectManagementService, {
objectTypes: [ObjectManagement.NodeType.ServerLevelLogin, ObjectManagement.NodeType.ServerLevelServerRole], objectTypes: [ObjectManagement.NodeType.ServerLevelLogin, ObjectManagement.NodeType.ServerLevelServerRole],
selectAllObjectTypes: true,
multiSelect: true, multiSelect: true,
contextId: this.contextId, contextId: this.contextId,
title: localizedConstants.SelectServerRoleMemberDialogTitle title: localizedConstants.SelectServerRoleMemberDialogTitle
}); });
await dialog.open(); await dialog.open();
const result = await dialog.waitForClose(); const result = await dialog.waitForClose();
this.addMembers(result.selectedObjects.map(r => r.name)); await this.addMembers(result.selectedObjects.map(r => r.name));
}, },
async () => { async () => {
if (this.memberTable.selectedRows.length === 1) { if (this.memberTable.selectedRows.length === 1) {
this.removeMember(this.memberTable.selectedRows[0]); await this.removeMember(this.memberTable.selectedRows[0]);
} }
}); });
this.memberSection = this.createGroup(localizedConstants.MemberSectionHeader, [this.memberTable, buttonContainer]); this.memberSection = this.createGroup(localizedConstants.MemberSectionHeader, [this.memberTable, buttonContainer]);
} }
private addMembers(names: string[]): void { private async addMembers(names: string[]): Promise<void> {
names.forEach(n => { names.forEach(n => {
if (this.objectInfo.members.indexOf(n) === -1) { if (this.objectInfo.members.indexOf(n) === -1) {
this.objectInfo.members.push(n); this.objectInfo.members.push(n);
} }
}); });
this.updateMembersTable(); await this.updateMembersTable();
} }
private removeMember(idx: number): void { private async removeMember(idx: number): Promise<void> {
this.objectInfo.members.splice(idx, 1); this.objectInfo.members.splice(idx, 1);
this.updateMembersTable(); await this.updateMembersTable();
} }
private updateMembersTable(): void { private async updateMembersTable(): Promise<void> {
this.setTableData(this.memberTable, this.objectInfo.members.map(m => [m])); await this.setTableData(this.memberTable, this.objectInfo.members.map(m => [m]));
this.onFormFieldChange(); this.onFormFieldChange();
} }

View File

@@ -3,14 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase'; import { ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { IObjectManagementService, ObjectManagement } from 'mssql'; import { IObjectManagementService, ObjectManagement } from 'mssql';
import * as localizedConstants from '../localizedConstants'; import * as localizedConstants from '../localizedConstants';
import { AlterUserDocUrl, CreateUserDocUrl } from '../constants'; import { AlterUserDocUrl, CreateUserDocUrl } from '../constants';
import { isValidSQLPassword } from '../utils'; import { isValidSQLPassword } from '../utils';
import { DefaultMaxTableHeight } from '../../ui/dialogBase'; import { DefaultMaxTableRowCount } from '../../ui/dialogBase';
import { PrincipalDialogBase } from './principalDialogBase';
export class UserDialog extends ObjectManagementDialogBase<ObjectManagement.User, ObjectManagement.UserViewInfo> { export class UserDialog extends PrincipalDialogBase<ObjectManagement.User, ObjectManagement.UserViewInfo> {
private generalSection: azdata.GroupContainer; private generalSection: azdata.GroupContainer;
private ownedSchemaSection: azdata.GroupContainer; private ownedSchemaSection: azdata.GroupContainer;
private membershipSection: azdata.GroupContainer; private membershipSection: azdata.GroupContainer;
@@ -31,7 +32,7 @@ export class UserDialog extends ObjectManagementDialogBase<ObjectManagement.User
private membershipTable: azdata.TableComponent; private membershipTable: azdata.TableComponent;
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) { constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options); super(objectManagementService, options, true);
} }
protected override get helpUrl(): string { protected override get helpUrl(): string {
@@ -61,12 +62,13 @@ export class UserDialog extends ObjectManagementDialogBase<ObjectManagement.User
return errors; return errors;
} }
protected async initializeUI(): Promise<void> { protected override async initializeUI(): Promise<void> {
await super.initializeUI();
this.initializeGeneralSection(); this.initializeGeneralSection();
this.initializeOwnedSchemaSection(); this.initializeOwnedSchemaSection();
this.initializeMembershipSection(); this.initializeMembershipSection();
this.initializeAdvancedSection(); this.initializeAdvancedSection();
this.formContainer.addItems([this.generalSection, this.ownedSchemaSection, this.membershipSection, this.advancedSection]); this.formContainer.addItems([this.generalSection, this.ownedSchemaSection, this.membershipSection, this.securableSection, this.advancedSection], this.getSectionItemLayout());
setTimeout(() => { setTimeout(() => {
this.setViewByUserType(); this.setViewByUserType();
}, 100); }, 100);
@@ -94,7 +96,7 @@ export class UserDialog extends ObjectManagementDialogBase<ObjectManagement.User
this.loginDropdown = this.createDropdown(localizedConstants.LoginText, async (newValue) => { this.loginDropdown = this.createDropdown(localizedConstants.LoginText, async (newValue) => {
this.objectInfo.loginName = newValue; this.objectInfo.loginName = newValue;
}, this.viewInfo.logins, this.objectInfo.loginName, this.options.isNewObject); }, this.options.isNewObject ? this.viewInfo.logins : [this.objectInfo.loginName], this.objectInfo.loginName, this.options.isNewObject);
this.loginContainer = this.createLabelInputContainer(localizedConstants.LoginText, this.loginDropdown); this.loginContainer = this.createLabelInputContainer(localizedConstants.LoginText, this.loginDropdown);
this.passwordInput = this.createPasswordInputBox(localizedConstants.PasswordText, async (newValue) => { this.passwordInput = this.createPasswordInputBox(localizedConstants.PasswordText, async (newValue) => {
@@ -119,7 +121,7 @@ export class UserDialog extends ObjectManagementDialogBase<ObjectManagement.User
[localizedConstants.SchemaText], [localizedConstants.SchemaText],
this.viewInfo.schemas, this.viewInfo.schemas,
this.objectInfo.ownedSchemas, this.objectInfo.ownedSchemas,
DefaultMaxTableHeight, DefaultMaxTableRowCount,
(item) => { (item) => {
// It is not allowed to have unassigned schema. // It is not allowed to have unassigned schema.
return this.objectInfo.ownedSchemas.indexOf(item) === -1; return this.objectInfo.ownedSchemas.indexOf(item) === -1;
@@ -157,6 +159,11 @@ export class UserDialog extends ObjectManagementDialogBase<ObjectManagement.User
this.addItem(this.generalSection, this.confirmPasswordContainer); this.addItem(this.generalSection, this.confirmPasswordContainer);
this.addItem(this.formContainer, this.advancedSection); this.addItem(this.formContainer, this.advancedSection);
break; break;
case ObjectManagement.UserType.WindowsUser:
if (this.objectInfo.loginName) {
this.addItem(this.generalSection, this.loginContainer);
}
break;
default: default:
break; break;
} }

View File

@@ -11,13 +11,13 @@ import * as uiLoc from '../ui/localizedConstants';
export const DefaultLabelWidth = 150; export const DefaultLabelWidth = 150;
export const DefaultInputWidth = 300; export const DefaultInputWidth = 300;
export const DefaultTableWidth = DefaultInputWidth + DefaultLabelWidth; export const DefaultTableWidth = DefaultInputWidth + DefaultLabelWidth;
export const DefaultMaxTableHeight = 400; export const DefaultMaxTableRowCount = 10;
export const DefaultMinTableRowCount = 1; export const DefaultMinTableRowCount = 1;
export const TableRowHeight = 25; const TableRowHeight = 25;
export const TableColumnHeaderHeight = 30; const TableColumnHeaderHeight = 30;
export function getTableHeight(rowCount: number, minRowCount: number = DefaultMinTableRowCount, maxHeight: number = DefaultMaxTableHeight): number { export function getTableHeight(rowCount: number, minRowCount: number = DefaultMinTableRowCount, maxRowCount: number = DefaultMaxTableRowCount): number {
return Math.min(Math.max(rowCount, minRowCount) * TableRowHeight + TableColumnHeaderHeight, maxHeight); return Math.min(Math.max(rowCount, minRowCount), maxRowCount) * TableRowHeight + TableColumnHeaderHeight;
} }
export type TableListItemEnabledStateGetter<T> = (item: T) => boolean; export type TableListItemEnabledStateGetter<T> = (item: T) => boolean;
@@ -168,7 +168,7 @@ export abstract class DialogBase<DialogResult> {
columnNames: string[], columnNames: string[],
allItems: T[], allItems: T[],
selectedItems: T[], selectedItems: T[],
maxHeight: number = DefaultMaxTableHeight, maxRowCount: number = DefaultMaxTableRowCount,
enabledStateGetter: TableListItemEnabledStateGetter<T> = DefaultTableListItemEnabledStateGetter, enabledStateGetter: TableListItemEnabledStateGetter<T> = DefaultTableListItemEnabledStateGetter,
rowValueGetter: TableListItemValueGetter<T> = DefaultTableListItemValueGetter, rowValueGetter: TableListItemValueGetter<T> = DefaultTableListItemValueGetter,
itemComparer: TableListItemComparer<T> = DefaultTableListItemComparer): azdata.TableComponent { itemComparer: TableListItemComparer<T> = DefaultTableListItemComparer): azdata.TableComponent {
@@ -179,7 +179,7 @@ export abstract class DialogBase<DialogResult> {
data: data, data: data,
columns: [ columns: [
{ {
value: uiLoc.SelectedText, value: uiLoc.SelectText,
type: azdata.ColumnType.checkBox, type: azdata.ColumnType.checkBox,
options: { actionOnCheckbox: azdata.ActionOnCellCheckboxCheck.customAction } options: { actionOnCheckbox: azdata.ActionOnCellCheckboxCheck.customAction }
}, ...columnNames.map(name => { }, ...columnNames.map(name => {
@@ -187,7 +187,7 @@ export abstract class DialogBase<DialogResult> {
}) })
], ],
width: DefaultTableWidth, width: DefaultTableWidth,
height: getTableHeight(data.length, DefaultMinTableRowCount, maxHeight) height: getTableHeight(data.length, DefaultMinTableRowCount, maxRowCount)
} }
).component(); ).component();
this.disposables.push(table.onCellAction!((arg: azdata.ICheckboxCellActionEventArgs) => { this.disposables.push(table.onCellAction!((arg: azdata.ICheckboxCellActionEventArgs) => {
@@ -203,9 +203,11 @@ export abstract class DialogBase<DialogResult> {
return table; return table;
} }
protected setTableData(table: azdata.TableComponent, data: any[][], maxHeight: number = DefaultMaxTableHeight) { protected async setTableData(table: azdata.TableComponent, data: any[][], maxRowCount: number = DefaultMaxTableRowCount) {
table.data = data; await table.updateProperties({
table.height = getTableHeight(data.length, DefaultMinTableRowCount, maxHeight); data: data,
height: getTableHeight(data.length, DefaultMinTableRowCount, maxRowCount)
});
} }
protected getDataForTableList<T>( protected getDataForTableList<T>(
@@ -221,14 +223,14 @@ export abstract class DialogBase<DialogResult> {
}); });
} }
protected createTable(ariaLabel: string, columns: azdata.TableColumn[], data: any[][], maxHeight: number = DefaultMaxTableHeight): azdata.TableComponent { protected createTable(ariaLabel: string, columns: string[], data: any[][], maxRowCount: number = DefaultMaxTableRowCount): azdata.TableComponent {
const table = this.modelView.modelBuilder.table().withProps( const table = this.modelView.modelBuilder.table().withProps(
{ {
ariaLabel: ariaLabel, ariaLabel: ariaLabel,
data: data, data: data,
columns: columns, columns: columns,
width: DefaultTableWidth, width: DefaultTableWidth,
height: getTableHeight(data.length, DefaultMinTableRowCount, maxHeight) height: getTableHeight(data.length, DefaultMinTableRowCount, maxRowCount)
} }
).component(); ).component();
return table; return table;
@@ -238,7 +240,8 @@ export abstract class DialogBase<DialogResult> {
let addButton: azdata.ButtonComponent; let addButton: azdata.ButtonComponent;
let removeButton: azdata.ButtonComponent; let removeButton: azdata.ButtonComponent;
const updateButtons = () => { const updateButtons = () => {
removeButton.enabled = table.selectedRows.length > 0; this.onFormFieldChange();
removeButton.enabled = table.selectedRows?.length === 1 && table.selectedRows[0] !== -1 && table.selectedRows[0] < table.data.length;
} }
addButton = this.createButton(uiLoc.AddText, addButtonAriaLabel, async () => { addButton = this.createButton(uiLoc.AddText, addButtonAriaLabel, async () => {
await addHandler(); await addHandler();
@@ -246,6 +249,9 @@ export abstract class DialogBase<DialogResult> {
}); });
removeButton = this.createButton(uiLoc.RemoveText, removeButtonAriaLabel, async () => { removeButton = this.createButton(uiLoc.RemoveText, removeButtonAriaLabel, async () => {
await removeHandler(); await removeHandler();
if (table.selectedRows.length === 1 && table.selectedRows[0] >= table.data.length) {
table.selectedRows = [table.data.length - 1];
}
updateButtons(); updateButtons();
}, false); }, false);
this.disposables.push(table.onRowSelected(() => { this.disposables.push(table.onRowSelected(() => {
@@ -308,12 +314,12 @@ export abstract class DialogBase<DialogResult> {
} }
} }
protected addItem(container: azdata.DivContainer | azdata.FlexContainer, item: azdata.Component, index?: number): void { protected addItem(container: azdata.DivContainer | azdata.FlexContainer, item: azdata.Component, itemLayout?: azdata.FlexItemLayout, index?: number): void {
if (container.items.indexOf(item) === -1) { if (container.items.indexOf(item) === -1) {
if (index === undefined) { if (index === undefined) {
container.addItem(item); container.addItem(item, itemLayout);
} else { } else {
container.insertItem(item, index); container.insertItem(item, index, itemLayout);
} }
} }
} }
@@ -327,6 +333,10 @@ export abstract class DialogBase<DialogResult> {
private createFormContainer(items: azdata.Component[]): azdata.DivContainer { private createFormContainer(items: azdata.Component[]): azdata.DivContainer {
return this.modelView.modelBuilder.divContainer().withLayout({ width: 'calc(100% - 20px)', height: 'calc(100% - 20px)' }).withProps({ return this.modelView.modelBuilder.divContainer().withLayout({ width: 'calc(100% - 20px)', height: 'calc(100% - 20px)' }).withProps({
CSSStyles: { 'padding': '10px' } CSSStyles: { 'padding': '10px' }
}).withItems(items, { CSSStyles: { 'margin-block-end': '10px' } }).component(); }).withItems(items, this.getSectionItemLayout()).component();
}
protected getSectionItemLayout(): azdata.FlexItemLayout {
return { CSSStyles: { 'margin-block-end': '5px' } };
} }
} }

View File

@@ -11,7 +11,7 @@ export const YesText: string = localize('mssql.ui.yesText', "Yes");
export const OkText: string = localize('mssql.ui.OkText', "OK"); export const OkText: string = localize('mssql.ui.OkText', "OK");
export const LoadingDialogText: string = localize('mssql.ui.loadingDialog', "Loading dialog..."); export const LoadingDialogText: string = localize('mssql.ui.loadingDialog', "Loading dialog...");
export const ScriptText: string = localize('mssql.ui.scriptText', "Script"); export const ScriptText: string = localize('mssql.ui.scriptText', "Script");
export const SelectedText = localize('objectManagement.selectedLabel', "Selected"); export const SelectText = localize('objectManagement.selectLabel', "Select");
export const AddText = localize('objectManagement.addText', "Add…"); export const AddText = localize('objectManagement.addText', "Add…");
export const RemoveText = localize('objectManagement.removeText', "Remove"); export const RemoveText = localize('objectManagement.removeText', "Remove");
export const NoActionScriptedMessage: string = localize('mssql.ui.noActionScriptedMessage', "There is no action to be scripted."); export const NoActionScriptedMessage: string = localize('mssql.ui.noActionScriptedMessage', "There is no action to be scripted.");

View File

@@ -295,7 +295,8 @@ export function createViewContext(): ViewTestContext {
columns: [] as string[], columns: [] as string[],
onRowSelected: onClick.event, onRowSelected: onClick.event,
onCellAction: onClick.event, onCellAction: onClick.event,
appendData: (_data: any[][]) => undefined appendData: (_data: any[][]) => undefined,
setActiveCell: (_row: number, _column: number) => undefined
}); });
let tableBuilder: azdata.ComponentBuilder<azdata.TableComponent, azdata.TableComponentProperties> = { let tableBuilder: azdata.ComponentBuilder<azdata.TableComponent, azdata.TableComponentProperties> = {
component: () => table(), component: () => table(),

View File

@@ -1947,4 +1947,11 @@ declare module 'azdata' {
isPrimary: boolean; isPrimary: boolean;
} }
} }
export interface TableComponent {
/**
* Set active cell.
*/
setActiveCell(row: number, column: number): void;
}
} }

View File

@@ -32,7 +32,8 @@ export enum ComponentEventType {
export enum ModelViewAction { export enum ModelViewAction {
SelectTab = 'selectTab', SelectTab = 'selectTab',
AppendData = 'appendData', AppendData = 'appendData',
Filter = 'filter' Filter = 'filter',
SetActiveCell = 'setActiveCell'
} }
/** /**

View File

@@ -1503,6 +1503,10 @@ class TableComponentWrapper extends ComponentWrapper implements azdata.TableComp
public appendData(v: any[][]): Thenable<void> { public appendData(v: any[][]): Thenable<void> {
return this.doAction(ModelViewAction.AppendData, v); return this.doAction(ModelViewAction.AppendData, v);
} }
public setActiveCell(row: number, column: number): void {
this.doAction(ModelViewAction.SetActiveCell, row, column);
}
} }
class DropDownWrapper extends ComponentWrapper implements azdata.DropDownComponent { class DropDownWrapper extends ComponentWrapper implements azdata.DropDownComponent {

View File

@@ -186,7 +186,8 @@ export enum ModelComponentTypes {
export enum ModelViewAction { export enum ModelViewAction {
SelectTab = 'selectTab', SelectTab = 'selectTab',
AppendData = 'appendData', AppendData = 'appendData',
Filter = 'filter' Filter = 'filter',
SetActiveCell = 'setActiveCell'
} }
export enum ColumnSizingMode { export enum ColumnSizingMode {

View File

@@ -40,6 +40,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { ITableService } from 'sql/workbench/services/table/browser/tableService'; import { ITableService } from 'sql/workbench/services/table/browser/tableService';
import { deepClone, equals } from 'vs/base/common/objects';
export enum ColumnSizingMode { export enum ColumnSizingMode {
ForceFit = 0, // all columns will be sized to fit in viewable space, no horiz scroll bar ForceFit = 0, // all columns will be sized to fit in viewable space, no horiz scroll bar
@@ -358,24 +359,27 @@ export default class TableComponent extends ComponentBase<azdata.TableComponentP
} }
public override setProperties(properties: { [key: string]: any; }): void { public override setProperties(properties: { [key: string]: any; }): void {
const oldColumns = deepClone(this.columns);
super.setProperties(properties); super.setProperties(properties);
this._tableData.clear(); this._tableData.clear();
this._tableData.push(this.transformData(this.data, this.columns)); this._tableData.push(this.transformData(this.data, this.columns));
if (!equals(oldColumns, this.columns)) {
this._tableColumns = this.transformColumns(this.columns); this._tableColumns = this.transformColumns(this.columns);
this._table.columns = this._tableColumns; this._table.columns = this._tableColumns;
this._checkboxColumns.forEach((column, columnName) => { this.registerPlugins(columnName, column); })
Object.keys(this._buttonColumns).forEach(col => this.registerPlugins(col, this._buttonColumns[col]));
Object.keys(this._hyperlinkColumns).forEach(col => this.registerPlugins(col, this._hyperlinkColumns[col]));
Object.keys(this._contextMenuColumns).forEach(col => this.registerPlugins(col, this._contextMenuColumns[col]));
this._table.columns = this._tableColumns;
this._table.autosizeColumns();
}
this._table.setData(this._tableData); this._table.setData(this._tableData);
this._table.setTableTitle(this.title); this._table.setTableTitle(this.title);
if (this.selectedRows) { if (this.selectedRows) {
this._table.setSelectedRows(this.selectedRows); this._table.setSelectedRows(this.selectedRows);
} }
this._checkboxColumns.forEach((column, columnName) => {
this.registerPlugins(columnName, column);
})
Object.keys(this._buttonColumns).forEach(col => this.registerPlugins(col, this._buttonColumns[col]));
Object.keys(this._hyperlinkColumns).forEach(col => this.registerPlugins(col, this._hyperlinkColumns[col]));
Object.keys(this._contextMenuColumns).forEach(col => this.registerPlugins(col, this._contextMenuColumns[col]));
if (this.headerFilter === true) { if (this.headerFilter === true) {
this.registerFilterPlugin(); this.registerFilterPlugin();
this._tableData.clearFilter(); this._tableData.clearFilter();
@@ -433,7 +437,8 @@ export default class TableComponent extends ComponentBase<azdata.TableComponentP
width: col.width, width: col.width,
cssClass: col.cssClass, cssClass: col.cssClass,
headerCssClass: col.headerCssClass, headerCssClass: col.headerCssClass,
actionOnCheck: checkboxAction actionOnCheck: checkboxAction,
columnId: `checkbox-column-${index}`,
}, index)); }, index));
this._register(this._checkboxColumns.get(col.value).onChange((state) => { this._register(this._checkboxColumns.get(col.value).onChange((state) => {
@@ -589,7 +594,6 @@ export default class TableComponent extends ComponentBase<azdata.TableComponentP
private registerPlugins(col: string, plugin: CheckboxSelectColumn<{}> | ButtonColumn<{}> | HyperlinkColumn<{}> | ContextMenuColumn<{}>): void { private registerPlugins(col: string, plugin: CheckboxSelectColumn<{}> | ButtonColumn<{}> | HyperlinkColumn<{}> | ContextMenuColumn<{}>): void {
const index = 'index' in plugin ? plugin.index : this.columns?.findIndex(x => x === col || ('value' in x && x['value'] === col)); const index = 'index' in plugin ? plugin.index : this.columns?.findIndex(x => x === col || ('value' in x && x['value'] === col));
if (index >= 0) { if (index >= 0) {
this._tableColumns.splice(index, 0, plugin.definition); this._tableColumns.splice(index, 0, plugin.definition);
@@ -598,10 +602,6 @@ export default class TableComponent extends ComponentBase<azdata.TableComponentP
this._pluginsRegisterStatus[col] = true; this._pluginsRegisterStatus[col] = true;
} }
} }
this._table.columns = this._tableColumns;
this._table.autosizeColumns();
} }
@@ -723,6 +723,10 @@ export default class TableComponent extends ComponentBase<azdata.TableComponentP
switch (action) { switch (action) {
case ModelViewAction.AppendData: case ModelViewAction.AppendData:
this.appendData(args[0]); this.appendData(args[0]);
break;
case ModelViewAction.SetActiveCell:
this._table.grid.setActiveCell(args[0], args[1]);
break;
} }
} }