diff --git a/extensions/mssql/src/hdfs/aclEntry.ts b/extensions/mssql/src/hdfs/aclEntry.ts index ac458ae8fe..f1da8fac97 100644 --- a/extensions/mssql/src/hdfs/aclEntry.ts +++ b/extensions/mssql/src/hdfs/aclEntry.ts @@ -5,6 +5,7 @@ import { IconPathHelper, IconPath } from '../iconHelper'; import { groupBy } from '../util/arrays'; +import * as loc from '../localizedConstants'; /** * The permission status of an HDFS path - this consists of : @@ -353,17 +354,17 @@ export function parseAclPermissionFromOctal(octal: string): { sticky: boolean, o }; } -export function getImageForType(type: AclType | PermissionType): IconPath { +export function getImageForType(type: AclType | PermissionType): { iconPath: IconPath, title: string } { switch (type) { case AclType.user: case PermissionType.owner: - return IconPathHelper.user; + return { iconPath: IconPathHelper.user, title: loc.owner }; case AclType.group: case PermissionType.group: case PermissionType.other: - return IconPathHelper.group; + return { iconPath: IconPathHelper.group, title: loc.group }; } - return { dark: '', light: '' }; + return { iconPath: { dark: '', light: '' }, title: '' }; } /** diff --git a/extensions/mssql/src/hdfs/ui/hdfsManageAccessDialog.ts b/extensions/mssql/src/hdfs/ui/hdfsManageAccessDialog.ts index 6a09a403ae..35d20c2929 100644 --- a/extensions/mssql/src/hdfs/ui/hdfsManageAccessDialog.ts +++ b/extensions/mssql/src/hdfs/ui/hdfsManageAccessDialog.ts @@ -6,7 +6,7 @@ import * as azdata from 'azdata'; import { HdfsModel } from '../hdfsModel'; import { IFileSource } from '../../objectExplorerNodeProvider/fileSources'; -import { PermissionStatus, AclEntry, AclType, getImageForType, AclEntryScope, AclEntryPermission } from '../../hdfs/aclEntry'; +import { PermissionStatus, AclEntry, AclType, getImageForType, AclEntryScope, AclEntryPermission, PermissionType } from '../../hdfs/aclEntry'; import { cssStyles } from './uiConstants'; import * as loc from '../../localizedConstants'; import { HdfsError } from '../webhdfs'; @@ -15,13 +15,10 @@ import { IconPathHelper } from '../../iconHelper'; import { HdfsFileType } from '../fileStatus'; const permissionsTypeIconColumnWidth = 35; -const permissionsNameColumnWidth = 250; -const permissionsInheritColumnWidth = 50; -const permissionsReadColumnWidth = 50; -const permissionsWriteColumnWidth = 50; -const permissionsExecuteColumnWidth = 50; const permissionsDeleteColumnWidth = 50; +const permissionsCheckboxColumnWidth = 50; + const permissionsRowHeight = 35; const locationLabelHeight = 23; // Fits the text size without too much white space @@ -38,8 +35,9 @@ export class ManageAccessDialog { private hdfsModel: HdfsModel; private viewInitialized: boolean = false; - + private modelInitialized: boolean = false; private modelBuilder: azdata.ModelBuilder; + private rootContainer: azdata.FlexContainer; private rootLoadingComponent: azdata.LoadingComponent; private stickyCheckbox: azdata.CheckBoxComponent; private inheritDefaultsCheckbox: azdata.CheckBoxComponent; @@ -48,7 +46,6 @@ export class ManageAccessDialog { private addUserOrGroupInput: azdata.InputBoxComponent; private dialog: azdata.window.Dialog; private applyRecursivelyButton: azdata.window.Button; - private defaultSectionComponents: azdata.Component[] = []; private posixPermissionCheckboxesMapping: PermissionCheckboxesMapping[] = []; private namedSectionInheritCheckboxes: azdata.CheckBoxComponent[] = []; private addUserOrGroupSelectedType: AclType; @@ -86,133 +83,15 @@ export class ManageAccessDialog { tab.registerContent(async (modelView: azdata.ModelView) => { this.modelBuilder = modelView.modelBuilder; - const rootContainer = modelView.modelBuilder.flexContainer() + this.rootContainer = modelView.modelBuilder.flexContainer() .withLayout({ flexFlow: 'column', width: '100%', height: '100%' }) .component(); - this.rootLoadingComponent = modelView.modelBuilder.loadingComponent().withItem(rootContainer).component(); + this.rootLoadingComponent = modelView.modelBuilder.loadingComponent().withItem(this.rootContainer).component(); - // We nest the content inside another container for the margins - getting them on the root container isn't supported - const contentContainer = modelView.modelBuilder.flexContainer() - .withLayout({ flexFlow: 'column', width: '100%', height: '100%' }) - .component(); - rootContainer.addItem(contentContainer, { CSSStyles: { 'margin-left': '20px', 'margin-right': '20px' } }); - - const locationContainer = modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'center' }).component(); - - const locationLabel = modelView.modelBuilder.text() - .withProperties({ - value: loc.locationTitle, - CSSStyles: { ...cssStyles.titleCss } - }).component(); - - const pathLabel = modelView.modelBuilder.text() - .withProperties({ - value: this.hdfsPath, - title: this.hdfsPath, - height: locationLabelHeight, - CSSStyles: { 'user-select': 'text', 'overflow': 'hidden', 'text-overflow': 'ellipsis', ...cssStyles.titleCss } - }).component(); - - locationContainer.addItem(locationLabel, - { - flex: '0 0 auto', - CSSStyles: { 'margin-bottom': '5px' } - }); - locationContainer.addItem(pathLabel, - { - flex: '1 1 auto', - CSSStyles: { 'border': '1px solid #ccc', 'padding': '5px', 'margin-left': '10px', 'min-height': `${locationLabelHeight}px` } - }); - - contentContainer.addItem(locationContainer, { flex: '0 0 auto', CSSStyles: { 'margin-top': '20px' } }); - - // ===================== - // = Permissions Title = - // ===================== - const permissionsTitle = modelView.modelBuilder.text() - .withProperties({ value: loc.permissionsHeader }) - .component(); - contentContainer.addItem(permissionsTitle, { CSSStyles: { 'margin-top': '15px', ...cssStyles.titleCss } }); - - // ============================= - // = POSIX permissions section = - // ============================= - - const posixPermissionsContainer = this.createPermissionsHeaderRow(modelView.modelBuilder, '', /*includeInherit*/false, /*includeStickyAndInherit*/true); - contentContainer.addItem(posixPermissionsContainer, { CSSStyles: { ...cssStyles.tableHeaderLayoutCss } }); - - // Empty initially - this is going to eventually be populated with the owner/owning/other group permissions - this.posixPermissionsContainer = modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component(); - contentContainer.addItem(this.posixPermissionsContainer, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '20px' } }); - - // =========================== - // = Add User Or Group Input = - // =========================== - - const addUserOrGroupTitle = modelView.modelBuilder.text() - .withProperties({ value: loc.addUserOrGroupHeader, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '10px' } }) - .component(); - contentContainer.addItem(addUserOrGroupTitle, { CSSStyles: { 'margin-top': '15px', ...cssStyles.titleCss } }); - - const typeContainer = modelView.modelBuilder.flexContainer().withProperties({ flexFlow: 'row' }).component(); - const aclEntryTypeGroup = 'aclEntryType'; - const userTypeButton = this.createRadioButton(modelView.modelBuilder, loc.userLabel, aclEntryTypeGroup, AclType.user); - const groupTypeButton = this.createRadioButton(modelView.modelBuilder, loc.groupLabel, aclEntryTypeGroup, AclType.group); - userTypeButton.checked = true; - this.addUserOrGroupSelectedType = AclType.user; - - typeContainer.addItems([userTypeButton, groupTypeButton], { flex: '0 0 auto' }); - contentContainer.addItem(typeContainer, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '5px' } }); - const addUserOrGroupInputRow = modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component(); - - this.addUserOrGroupInput = modelView.modelBuilder.inputBox() - .withProperties({ - inputType: 'text', - placeHolder: loc.enterNamePlaceholder, - width: 250, - stopEnterPropagation: true - }) - .component(); - this.addUserOrGroupInput.onEnterKeyPressed((value: string) => { - this.hdfsModel.createAndAddAclEntry(value, this.addUserOrGroupSelectedType); - this.addUserOrGroupInput.value = ''; - }); - const addUserOrGroupButton = modelView.modelBuilder.button().withProperties({ label: loc.addLabel, width: 75 }).component(); - addUserOrGroupButton.onDidClick(() => { - this.hdfsModel.createAndAddAclEntry(this.addUserOrGroupInput.value, this.addUserOrGroupSelectedType); - this.addUserOrGroupInput.value = ''; - }); - addUserOrGroupButton.enabled = false; // Init to disabled since we don't have any name entered in yet - this.addUserOrGroupInput.onTextChanged(() => { - if (this.addUserOrGroupInput.value === '') { - addUserOrGroupButton.enabled = false; - } else { - addUserOrGroupButton.enabled = true; - } - }); - - addUserOrGroupInputRow.addItem(this.addUserOrGroupInput, { flex: '0 0 auto' }); - addUserOrGroupInputRow.addItem(addUserOrGroupButton, { flex: '0 0 auto', CSSStyles: { 'margin-left': '20px' } }); - - contentContainer.addItem(addUserOrGroupInputRow, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '20px' } }); - - // ================================================= - // = Named Users and Groups permissions header row = - // ================================================= - - const namedUsersAndGroupsPermissionsHeaderRow = this.createPermissionsHeaderRow(modelView.modelBuilder, loc.namedUsersAndGroupsHeader, /*includeInherit*/true, /*includeStickyAndInherit*/false); - contentContainer.addItem(namedUsersAndGroupsPermissionsHeaderRow, { CSSStyles: { ...cssStyles.tableHeaderLayoutCss } }); - - // Empty initially - this is eventually going to be populated with the ACL entries set for this path - this.namedUsersAndGroupsPermissionsContainer = modelView.modelBuilder.flexContainer() - .withLayout({ flexFlow: 'column' }) - .component(); - contentContainer.addItem(this.namedUsersAndGroupsPermissionsContainer, { flex: '1', CSSStyles: { 'overflow': 'scroll', 'min-height': '200px' } }); - - this.viewInitialized = true; - this.handlePermissionStatusUpdated(this.hdfsModel.permissionStatus); await modelView.initializeModel(this.rootLoadingComponent); + this.modelInitialized = true; + this.handlePermissionStatusUpdated(this.hdfsModel.permissionStatus); this.addUserOrGroupInput.focus(); }); this.dialog.content = [tab]; @@ -222,34 +101,228 @@ export class ManageAccessDialog { azdata.window.openDialog(this.dialog); } + private initializeView(permissionStatus: PermissionStatus): void { + // We nest the content inside another container for the margins - getting them on the root container isn't supported + const contentContainer = this.modelBuilder.flexContainer() + .withLayout({ flexFlow: 'column', width: '100%', height: '100%' }) + .component(); + this.rootContainer.addItem(contentContainer, { CSSStyles: { 'margin-left': '20px', 'margin-right': '20px' } }); + + const locationContainer = this.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'center' }).component(); + + const locationLabel = this.modelBuilder.text() + .withProperties({ + value: loc.locationTitle, + CSSStyles: { ...cssStyles.titleCss } + }).component(); + + const pathLabel = this.modelBuilder.text() + .withProperties({ + value: this.hdfsPath, + title: this.hdfsPath, + height: locationLabelHeight, + CSSStyles: { 'user-select': 'text', 'overflow': 'hidden', 'text-overflow': 'ellipsis', ...cssStyles.titleCss } + }).component(); + + locationContainer.addItem(locationLabel, + { + flex: '0 0 auto', + CSSStyles: { 'margin-bottom': '5px' } + }); + locationContainer.addItem(pathLabel, + { + flex: '1 1 auto', + CSSStyles: { 'border': '1px solid #ccc', 'padding': '5px', 'margin-left': '10px', 'min-height': `${locationLabelHeight}px` } + }); + + contentContainer.addItem(locationContainer, { flex: '0 0 auto', CSSStyles: { 'margin-top': '20px' } }); + + // ===================== + // = Permissions Title = + // ===================== + const permissionsTitle = this.modelBuilder.text() + .withProperties({ value: loc.permissionsHeader }) + .component(); + contentContainer.addItem(permissionsTitle, { CSSStyles: { 'margin-top': '15px', ...cssStyles.titleCss } }); + + // ==================== + // = Inherit Defaults = + // ==================== + + // Defaults are only settable for directories + if (this.hdfsModel.fileStatus.type === HdfsFileType.Directory) { + contentContainer.addItem(this.createInheritDefaultsCheckbox()); + } + + // ========== + // = Sticky = + // ========== + this.stickyCheckbox = this.modelBuilder.checkBox() + .withProperties({ + width: checkboxSize, + height: checkboxSize, + checked: permissionStatus.stickyBit, + label: loc.stickyLabel + }).component(); + this.stickyCheckbox.onChanged(() => { + this.hdfsModel.permissionStatus.stickyBit = this.stickyCheckbox.checked; + }); + contentContainer.addItem(this.stickyCheckbox); + + // ============================= + // = POSIX permissions section = + // ============================= + + const posixPermissionsSectionHeaderRow = this.createPermissionsSectionHeaderRow(0, 0); + contentContainer.addItem(posixPermissionsSectionHeaderRow, { CSSStyles: { ...cssStyles.tableHeaderLayoutCss } }); + + this.posixPermissionsContainer = this.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component(); + contentContainer.addItem(this.posixPermissionsContainer, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '20px' } }); + + // =========================== + // = Add User Or Group Input = + // =========================== + + const addUserOrGroupTitle = this.modelBuilder.text() + .withProperties({ value: loc.addUserOrGroupHeader, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '10px' } }) + .component(); + contentContainer.addItem(addUserOrGroupTitle, { CSSStyles: { 'margin-top': '15px', ...cssStyles.titleCss } }); + + const typeContainer = this.modelBuilder.flexContainer().withProperties({ flexFlow: 'row' }).component(); + const aclEntryTypeGroup = 'aclEntryType'; + const userTypeButton = this.createRadioButton(this.modelBuilder, loc.userLabel, aclEntryTypeGroup, AclType.user); + const groupTypeButton = this.createRadioButton(this.modelBuilder, loc.groupLabel, aclEntryTypeGroup, AclType.group); + userTypeButton.checked = true; + this.addUserOrGroupSelectedType = AclType.user; + + typeContainer.addItems([userTypeButton, groupTypeButton], { flex: '0 0 auto' }); + contentContainer.addItem(typeContainer, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '5px' } }); + const addUserOrGroupInputRow = this.modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component(); + + this.addUserOrGroupInput = this.modelBuilder.inputBox() + .withProperties({ + inputType: 'text', + placeHolder: loc.enterNamePlaceholder, + width: 250, + stopEnterPropagation: true + }) + .component(); + this.addUserOrGroupInput.onEnterKeyPressed((value: string) => { + this.hdfsModel.createAndAddAclEntry(value, this.addUserOrGroupSelectedType); + this.addUserOrGroupInput.value = ''; + }); + const addUserOrGroupButton = this.modelBuilder.button().withProperties({ label: loc.addLabel, width: 75 }).component(); + addUserOrGroupButton.onDidClick(() => { + this.hdfsModel.createAndAddAclEntry(this.addUserOrGroupInput.value, this.addUserOrGroupSelectedType); + this.addUserOrGroupInput.value = ''; + }); + addUserOrGroupButton.enabled = false; // Init to disabled since we don't have any name entered in yet + this.addUserOrGroupInput.onTextChanged(() => { + addUserOrGroupButton.enabled = this.addUserOrGroupInput.value !== ''; + }); + + addUserOrGroupInputRow.addItem(this.addUserOrGroupInput, { flex: '0 0 auto' }); + addUserOrGroupInputRow.addItem(addUserOrGroupButton, { flex: '0 0 auto', CSSStyles: { 'margin-left': '20px' } }); + + contentContainer.addItem(addUserOrGroupInputRow, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '20px' } }); + + // ================================================= + // = Named Users and Groups permissions header row = + // ================================================= + + const namedUsersAndGroupsSectionsHeaderRow = this.createPermissionsSectionHeaderRow(permissionsDeleteColumnWidth, permissionsCheckboxColumnWidth); + contentContainer.addItem(namedUsersAndGroupsSectionsHeaderRow, { CSSStyles: { ...cssStyles.tableHeaderLayoutCss } }); + + this.namedUsersAndGroupsPermissionsContainer = this.modelBuilder.flexContainer() + .withLayout({ flexFlow: 'column' }) + .component(); + contentContainer.addItem(this.namedUsersAndGroupsPermissionsContainer, { flex: '1', CSSStyles: { 'overflow': 'scroll', 'min-height': '200px' } }); + this.viewInitialized = true; + } + private handlePermissionStatusUpdated(permissionStatus: PermissionStatus): void { - if (!permissionStatus || !this.viewInitialized) { + if (!permissionStatus || !this.modelInitialized) { return; } + // If this is the first time go through and create the UI components now that we have a model to use + if (!this.viewInitialized) { + this.initializeView(permissionStatus); + } + this.stickyCheckbox.checked = permissionStatus.stickyBit; - this.inheritDefaultsCheckbox.checked = - !permissionStatus.owner.getPermission(AclEntryScope.default) && - !permissionStatus.group.getPermission(AclEntryScope.default) && - !permissionStatus.other.getPermission(AclEntryScope.default); - // Update display status for headers for the Default section - you can't set Default ACLs for non-directories so we just hide that column - this.defaultSectionComponents.forEach(component => component.display = this.hdfsModel.fileStatus.type === HdfsFileType.Directory ? '' : 'none'); + if (this.hdfsModel.fileStatus.type === HdfsFileType.Directory) { + this.inheritDefaultsCheckbox.checked = + !permissionStatus.owner.getPermission(AclEntryScope.default) && + !permissionStatus.group.getPermission(AclEntryScope.default) && + !permissionStatus.other.getPermission(AclEntryScope.default); + } + this.applyRecursivelyButton.hidden = this.hdfsModel.fileStatus.type !== HdfsFileType.Directory; - this.inheritDefaultsCheckbox.display = this.hdfsModel.fileStatus.type === HdfsFileType.Directory ? '' : 'none'; - // POSIX permission owner/group/other - const ownerPermissionsRow = this.createPermissionsRow(this.modelBuilder, permissionStatus.owner, /*includeDelete*/false, /*includeInherit*/false); - const owningGroupPermissionsRow = this.createPermissionsRow(this.modelBuilder, permissionStatus.group, /*includeDelete*/false, /*includeInherit*/false); - const otherPermissionsRow = this.createPermissionsRow(this.modelBuilder, permissionStatus.other, /*includeDelete*/false, /*includeInherit*/false); + this.posixPermissionsContainer.clearItems(); - this.posixPermissionsContainer.addItems([ownerPermissionsRow, owningGroupPermissionsRow, otherPermissionsRow], { CSSStyles: { 'border-bottom': cssStyles.tableBorderCss, 'border-top': cssStyles.tableBorderCss, 'margin-right': '14px' } }); + + const posixPermissionData = [permissionStatus.owner, permissionStatus.group, permissionStatus.other].map(aclEntry => { + return this.createPermissionsTableRow(aclEntry, false/*includeDelete*/, false/*includeInherit*/); + }); + + const posixPermissionsNamesColumnWidth = 800 + (this.hdfsModel.fileStatus.type === HdfsFileType.Directory ? 0 : permissionsCheckboxColumnWidth * 3); + const namedUsersAndGroupsPermissionsNamesColumnWidth = 700 + (this.hdfsModel.fileStatus.type === HdfsFileType.Directory ? 0 : permissionsCheckboxColumnWidth * 3); + + // Default set of columns that are always shown + let posixPermissionsColumns = [ + this.createTableColumn('', loc.userOrGroupIcon, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component), + this.createTableColumn('', loc.defaultUserAndGroups, posixPermissionsNamesColumnWidth, azdata.DeclarativeDataType.string), + this.createTableColumn(loc.readHeader, `${loc.accessHeader} ${loc.readHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component), + this.createTableColumn(loc.writeHeader, `${loc.accessHeader} ${loc.writeHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component), + this.createTableColumn(loc.executeHeader, `${loc.accessHeader} ${loc.executeHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component)]; + let namedUsersAndGroupsColumns = [ + this.createTableColumn('', loc.userOrGroupIcon, 50, azdata.DeclarativeDataType.component), + this.createTableColumn(loc.namedUsersAndGroupsHeader, loc.namedUsersAndGroupsHeader, namedUsersAndGroupsPermissionsNamesColumnWidth, azdata.DeclarativeDataType.string), + this.createTableColumn(loc.readHeader, `${loc.accessHeader} ${loc.readHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component), + this.createTableColumn(loc.writeHeader, `${loc.accessHeader} ${loc.writeHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component), + this.createTableColumn(loc.executeHeader, `${loc.accessHeader} ${loc.executeHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component)]; + + // Additional columns that are only shown for directories + if (this.hdfsModel.fileStatus.type === HdfsFileType.Directory) { + posixPermissionsColumns = posixPermissionsColumns.concat([ + this.createTableColumn(loc.readHeader, `${loc.defaultHeader} ${loc.readHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component), + this.createTableColumn(loc.writeHeader, `${loc.defaultHeader} ${loc.writeHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component), + this.createTableColumn(loc.executeHeader, `${loc.defaultHeader} ${loc.executeHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component) + ]); + namedUsersAndGroupsColumns = namedUsersAndGroupsColumns.concat([ + this.createTableColumn(loc.inheritDefaultsLabel, loc.inheritDefaultsLabel, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component), + this.createTableColumn(loc.readHeader, `${loc.defaultHeader} ${loc.readHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component), + this.createTableColumn(loc.writeHeader, `${loc.defaultHeader} ${loc.writeHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component), + this.createTableColumn(loc.executeHeader, `${loc.defaultHeader} ${loc.executeHeader}`, permissionsCheckboxColumnWidth, azdata.DeclarativeDataType.component), + ]); + } + namedUsersAndGroupsColumns.push(this.createTableColumn('', loc.deleteTitle, permissionsDeleteColumnWidth, azdata.DeclarativeDataType.component)); + + const posixPermissionsTable = this.modelBuilder.declarativeTable() + .withProperties( + { + columns: posixPermissionsColumns, + data: posixPermissionData + }).component(); + + this.posixPermissionsContainer.addItem(posixPermissionsTable, { CSSStyles: { 'margin-right': '12px' } }); this.namedUsersAndGroupsPermissionsContainer.clearItems(); - // Named users and groups - permissionStatus.aclEntries.forEach(entry => { - const namedEntryRow = this.createPermissionsRow(this.modelBuilder, entry, /*includeDelete*/true, /*includeInherit*/true); - this.namedUsersAndGroupsPermissionsContainer.addItem(namedEntryRow, { CSSStyles: { 'border-bottom': cssStyles.tableBorderCss, 'border-top': cssStyles.tableBorderCss } }); + + const namedUsersAndGroupsData = permissionStatus.aclEntries.map(aclEntry => { + return this.createPermissionsTableRow(aclEntry, true/*includeDelete*/, this.hdfsModel.fileStatus.type === HdfsFileType.Directory/*includeInherit*/); }); + const namedUsersAndGroupsTable = this.modelBuilder.declarativeTable() + .withProperties( + { + columns: namedUsersAndGroupsColumns, + data: namedUsersAndGroupsData + }).component(); + + this.namedUsersAndGroupsPermissionsContainer.addItem(namedUsersAndGroupsTable); + this.rootLoadingComponent.loading = false; } @@ -261,117 +334,138 @@ export class ManageAccessDialog { return button; } - private createPermissionsRow(builder: azdata.ModelBuilder, entry: AclEntry, includeDelete: boolean, includeInherit: boolean): azdata.FlexContainer { - const rowContainer = builder.flexContainer().withLayout({ flexFlow: 'row', height: permissionsRowHeight }).component(); + private createTableColumn(header: string, ariaLabel: string, width: number, type: azdata.DeclarativeDataType): azdata.DeclarativeTableColumn { + return { + displayName: header, + ariaLabel: ariaLabel, + valueType: type, + isReadOnly: true, + width: width, + headerCssStyles: { + 'border': 'none', + 'background-color': '#FFFFFF', + 'padding': '0px', + ...cssStyles.permissionsTableHeaderCss + }, + rowCssStyles: { + 'border-top': 'solid 1px #ccc', + 'border-bottom': 'solid 1px #ccc', + 'border-left': 'none', + 'border-right': 'none', + 'padding': '0px' + }, + }; + } - // Icon - const iconCell = builder.image() + private createImageComponent(type: AclType | PermissionType): azdata.ImageComponent { + const imageProperties = getImageForType(type); + return this.modelBuilder.image() .withProperties({ - iconPath: getImageForType(entry.type), + iconPath: imageProperties.iconPath, width: permissionsTypeIconColumnWidth, height: permissionsRowHeight, iconWidth: 20, - iconHeight: 20 - }) - .component(); - rowContainer.addItem(iconCell, { flex: '0 0 auto' }); + iconHeight: 20, + title: imageProperties.title + }).component(); + } - // Name - const nameCell = builder.text().withProperties({ value: entry.displayName }).component(); - rowContainer.addItem(nameCell); - - // Access - Read - const accessReadComponents = createCheckbox(builder, entry.getPermission(AclEntryScope.access).read, true, permissionsReadColumnWidth, permissionsRowHeight, `${loc.accessHeader} ${loc.readHeader}`); - rowContainer.addItem(accessReadComponents.container, { flex: '0 0 auto' }); + private createPermissionsTableRow(aclEntry: AclEntry, includeDelete: boolean, includeInherit: boolean): any[] { + // Access Read + const accessReadComponents = createCheckbox(this.modelBuilder, aclEntry.getPermission(AclEntryScope.access).read, true, permissionsCheckboxColumnWidth, permissionsRowHeight, `${loc.accessHeader} ${loc.readHeader}`); accessReadComponents.checkbox.onChanged(() => { - entry.getPermission(AclEntryScope.access).read = accessReadComponents.checkbox.checked; + aclEntry.getPermission(AclEntryScope.access).read = accessReadComponents.checkbox.checked; }); - // Access - Write - const accessWriteComponents = createCheckbox(builder, entry.getPermission(AclEntryScope.access).write, true, permissionsWriteColumnWidth, permissionsRowHeight, `${loc.accessHeader} ${loc.writeHeader}`); - rowContainer.addItem(accessWriteComponents.container, { flex: '0 0 auto' }); + // Access Write + const accessWriteComponents = createCheckbox(this.modelBuilder, aclEntry.getPermission(AclEntryScope.access).write, true, permissionsCheckboxColumnWidth, permissionsRowHeight, `${loc.accessHeader} ${loc.writeHeader}`); accessWriteComponents.checkbox.onChanged(() => { - entry.getPermission(AclEntryScope.access).write = accessWriteComponents.checkbox.checked; + aclEntry.getPermission(AclEntryScope.access).write = accessWriteComponents.checkbox.checked; }); - // Access - Execute - const accessExecuteComponents = createCheckbox(builder, entry.getPermission(AclEntryScope.access).execute, true, permissionsExecuteColumnWidth, permissionsRowHeight, `${loc.accessHeader} ${loc.executeHeader}`); - rowContainer.addItem(accessExecuteComponents.container, { flex: '0 0 auto', CSSStyles: { 'border-right': this.hdfsModel.fileStatus.type === HdfsFileType.Directory ? cssStyles.tableBorderCss : '' } }); + // Access Execute + const accessExecuteComponents = createCheckbox(this.modelBuilder, aclEntry.getPermission(AclEntryScope.access).execute, true, permissionsCheckboxColumnWidth, permissionsRowHeight, `${loc.accessHeader} ${loc.executeHeader}`); accessExecuteComponents.checkbox.onChanged(() => { - entry.getPermission(AclEntryScope.access).execute = accessExecuteComponents.checkbox.checked; + aclEntry.getPermission(AclEntryScope.access).execute = accessExecuteComponents.checkbox.checked; }); const permissionsCheckboxesMapping: PermissionCheckboxesMapping = { - model: entry, + model: aclEntry, access: { read: accessReadComponents.checkbox, write: accessWriteComponents.checkbox, execute: accessExecuteComponents.checkbox }, default: { read: undefined, write: undefined, execute: undefined } }; - // Only directories can set ACL defaults so we hide the column for non-directories + let row = [ + this.createImageComponent(aclEntry.type), + aclEntry.displayName, + accessReadComponents.container, + accessWriteComponents.container, + accessExecuteComponents.container + ]; + + // Default permissions can only be set on directories if (this.hdfsModel.fileStatus.type === HdfsFileType.Directory) { + const defaultPermission = aclEntry.getPermission(AclEntryScope.default); - const defaultPermission = entry.getPermission(AclEntryScope.default); + // Default Read + const defaultReadCheckboxComponents = createCheckbox(this.modelBuilder, defaultPermission && defaultPermission.read, !!defaultPermission, permissionsCheckboxColumnWidth, permissionsRowHeight, `${loc.defaultHeader} ${loc.readHeader}`); + defaultReadCheckboxComponents.checkbox.onChanged(() => { + aclEntry.getPermission(AclEntryScope.default).read = defaultReadCheckboxComponents.checkbox.checked; + }); - const defaultReadComponents = createCheckbox(builder, defaultPermission && defaultPermission.read, !!defaultPermission, permissionsReadColumnWidth, permissionsRowHeight, `${loc.defaultHeader} ${loc.readHeader}`); - const defaultWriteComponents = createCheckbox(builder, defaultPermission && defaultPermission.write, !!defaultPermission, permissionsWriteColumnWidth, permissionsRowHeight, `${loc.defaultHeader} ${loc.writeHeader}`); - const defaultExecuteComponents = createCheckbox(builder, defaultPermission && defaultPermission.execute, !!defaultPermission, permissionsExecuteColumnWidth, permissionsRowHeight, `${loc.defaultHeader} ${loc.executeHeader}`); - permissionsCheckboxesMapping.default = { read: defaultReadComponents.checkbox, write: defaultWriteComponents.checkbox, execute: defaultExecuteComponents.checkbox }; + // Default Write + const defaultWriteCheckboxComponents = createCheckbox(this.modelBuilder, defaultPermission && defaultPermission.write, !!defaultPermission, permissionsCheckboxColumnWidth, permissionsRowHeight, `${loc.defaultHeader} ${loc.writeHeader}`); + defaultWriteCheckboxComponents.checkbox.onChanged(() => { + aclEntry.getPermission(AclEntryScope.default).write = defaultWriteCheckboxComponents.checkbox.checked; + }); + + // Default Execute + const defaultExecuteCheckboxComponents = createCheckbox(this.modelBuilder, defaultPermission && defaultPermission.execute, !!defaultPermission, permissionsCheckboxColumnWidth, permissionsRowHeight, `${loc.defaultHeader} ${loc.executeHeader}`); + defaultExecuteCheckboxComponents.checkbox.onChanged(() => { + aclEntry.getPermission(AclEntryScope.default).execute = defaultExecuteCheckboxComponents.checkbox.checked; + }); + + permissionsCheckboxesMapping.default = { read: defaultReadCheckboxComponents.checkbox, write: defaultWriteCheckboxComponents.checkbox, execute: defaultExecuteCheckboxComponents.checkbox }; - // Default - Inherit if (includeInherit) { - const defaultInheritComponents = createCheckbox(builder, !defaultPermission, !this.inheritDefaultsCheckbox.checked, permissionsInheritColumnWidth, permissionsRowHeight, loc.inheritDefaultsLabel); - defaultInheritComponents.checkbox.onChanged(() => { - defaultReadComponents.checkbox.enabled = !defaultInheritComponents.checkbox.checked; - defaultWriteComponents.checkbox.enabled = !defaultInheritComponents.checkbox.checked; - defaultExecuteComponents.checkbox.enabled = !defaultInheritComponents.checkbox.checked; - if (defaultInheritComponents.checkbox.checked) { - entry.removePermission(AclEntryScope.default); - defaultReadComponents.checkbox.checked = false; - defaultWriteComponents.checkbox.checked = false; - defaultExecuteComponents.checkbox.checked = false; + const inheritCheckboxComponents = createCheckbox(this.modelBuilder, !defaultPermission, !this.inheritDefaultsCheckbox.checked, permissionsCheckboxColumnWidth, permissionsRowHeight, loc.inheritDefaultsLabel); + inheritCheckboxComponents.checkbox.onChanged(() => { + defaultReadCheckboxComponents.checkbox.enabled = !inheritCheckboxComponents.checkbox.checked; + defaultWriteCheckboxComponents.checkbox.enabled = !inheritCheckboxComponents.checkbox.checked; + defaultExecuteCheckboxComponents.checkbox.enabled = !inheritCheckboxComponents.checkbox.checked; + if (inheritCheckboxComponents.checkbox.checked) { + aclEntry.removePermission(AclEntryScope.default); + defaultReadCheckboxComponents.checkbox.checked = false; + defaultWriteCheckboxComponents.checkbox.checked = false; + defaultExecuteCheckboxComponents.checkbox.checked = false; } else { // Default to the access settings - this is what HDFS does if you don't // specify the complete set of default ACLs for owner, owning group and other const accessRead = accessReadComponents.checkbox.checked; const accessWrite = accessWriteComponents.checkbox.checked; const accessExecute = accessExecuteComponents.checkbox.checked; - defaultReadComponents.checkbox.checked = accessRead; - defaultWriteComponents.checkbox.checked = accessWrite; - defaultExecuteComponents.checkbox.checked = accessExecute; - entry.addPermission(AclEntryScope.default, + defaultReadCheckboxComponents.checkbox.checked = accessRead; + defaultWriteCheckboxComponents.checkbox.checked = accessWrite; + defaultExecuteCheckboxComponents.checkbox.checked = accessExecute; + aclEntry.addPermission(AclEntryScope.default, new AclEntryPermission(accessRead, accessWrite, accessExecute)); } }); - this.namedSectionInheritCheckboxes.push(defaultInheritComponents.checkbox); - rowContainer.addItem(defaultInheritComponents.container, { flex: '0 0 auto', CSSStyles: { 'border-right': cssStyles.tableBorderCss } }); + this.namedSectionInheritCheckboxes.push(inheritCheckboxComponents.checkbox); + row.push(inheritCheckboxComponents.container); } + this.posixPermissionCheckboxesMapping.push(permissionsCheckboxesMapping); - // Default - Read - rowContainer.addItem(defaultReadComponents.container, { flex: '0 0 auto' }); - defaultReadComponents.checkbox.onChanged(() => { - entry.getPermission(AclEntryScope.default).read = defaultReadComponents.checkbox.checked; - }); - - // Default - Write - rowContainer.addItem(defaultWriteComponents.container, { flex: '0 0 auto' }); - defaultWriteComponents.checkbox.onChanged(() => { - entry.getPermission(AclEntryScope.default).write = defaultWriteComponents.checkbox.checked; - }); - - // Default - Execute - rowContainer.addItem(defaultExecuteComponents.container, { flex: '0 0 auto' }); - defaultExecuteComponents.checkbox.onChanged(() => { - entry.getPermission(AclEntryScope.default).execute = defaultExecuteComponents.checkbox.checked; - }); + row = row.concat([ + defaultReadCheckboxComponents.container, + defaultWriteCheckboxComponents.container, + defaultExecuteCheckboxComponents.container + ]); } - this.posixPermissionCheckboxesMapping.push(permissionsCheckboxesMapping); - - const deleteContainer = builder.flexContainer().withLayout({ width: permissionsDeleteColumnWidth, height: permissionsRowHeight }).component(); - if (includeDelete) { - const deleteButton = builder.button() + const deleteButton = this.modelBuilder.button() .withProperties( { label: '', @@ -381,164 +475,125 @@ export class ManageAccessDialog { height: 20 }) .component(); - deleteButton.onDidClick(() => { this.hdfsModel.deleteAclEntry(entry); }); - deleteContainer.addItem(deleteButton); + deleteButton.onDidClick(() => { this.hdfsModel.deleteAclEntry(aclEntry); }); + row.push(deleteButton); } - rowContainer.addItem(deleteContainer, { flex: '0 0 auto', CSSStyles: { 'margin-top': '7px', 'margin-left': '5px' } }); - return rowContainer; + return row; } + private createInheritDefaultsCheckbox(): azdata.CheckBoxComponent { + this.inheritDefaultsCheckbox = this.modelBuilder.checkBox() + .withProperties({ + width: checkboxSize, + height: checkboxSize, + checked: false, // Will be set when we get the model update + label: loc.inheritDefaultsLabel + }) + .component(); + + this.inheritDefaultsCheckbox.onChanged(() => { + if (this.inheritDefaultsCheckbox.checked) { + this.namedSectionInheritCheckboxes.forEach(c => { + c.enabled = false; + c.checked = true; + }); + } else { + this.namedSectionInheritCheckboxes.forEach(c => { + c.enabled = true; + c.checked = false; + }); + } + // Go through each of the rows for owner/owning group/other and update + // their checkboxes based on the new value of the inherit checkbox + this.posixPermissionCheckboxesMapping.forEach(m => { + m.default.read.enabled = !this.inheritDefaultsCheckbox.checked; + m.default.write.enabled = !this.inheritDefaultsCheckbox.checked; + m.default.execute.enabled = !this.inheritDefaultsCheckbox.checked; + if (this.inheritDefaultsCheckbox.checked) { + m.model.removePermission(AclEntryScope.default); + m.default.read.checked = false; + m.default.write.checked = false; + m.default.execute.checked = false; + } else { + // Default to the access settings - this is what HDFS does if you don't + // specify the complete set of default ACLs for owner, owning group and other + const accessRead = m.access.read.checked; + const accessWrite = m.access.write.checked; + const accessExecute = m.access.execute.checked; + m.default.read.checked = accessRead; + m.default.write.checked = accessWrite; + m.default.execute.checked = accessExecute; + m.model.addPermission(AclEntryScope.default, new AclEntryPermission(accessRead, accessWrite, accessExecute)); + } + }); + }); + return this.inheritDefaultsCheckbox; + } /** * Creates the header row for the permissions tables. This contains headers for the name and read/write/execute for the * access section. If the path is for a directory then a default section is included for specifying default permissions. - * @param modelBuilder The builder used to create the model components - * @param nameColumnText The text to display for the name column + * @param rightSpacerWidth The amount of space to include on the right to correctly align the headers with the + * @param middleSpacerWidth The amount of space to include between the text to correctly align the headers with the table sections */ - private createPermissionsHeaderRow(modelBuilder: azdata.ModelBuilder, nameColumnText: string, includeInherit: boolean, includeStickyAndInherit: boolean): azdata.FlexContainer { - const rowsContainer = modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component(); - + private createPermissionsSectionHeaderRow(rightSpacerWidth: number, middleSpacerWidth: number): azdata.FlexContainer { // Section Headers - const sectionHeaderContainer = modelBuilder.flexContainer().withLayout({ flexFlow: 'row', justifyContent: 'flex-end' }).component(); - - if (includeStickyAndInherit) { - this.inheritDefaultsCheckbox = modelBuilder.checkBox() - .withProperties({ - width: checkboxSize, - height: checkboxSize, - checked: false, // Will be set when we get the model update - label: loc.inheritDefaultsLabel - }) - .component(); - - this.inheritDefaultsCheckbox.onChanged(() => { - if (this.inheritDefaultsCheckbox.checked) { - this.namedSectionInheritCheckboxes.forEach(c => { - c.enabled = false; - c.checked = true; - }); - } else { - this.namedSectionInheritCheckboxes.forEach(c => { - c.enabled = true; - c.checked = false; - }); - } - // Go through each of the rows for owner/owning group/other and update - // their checkboxes based on the new value of the inherit checkbox - this.posixPermissionCheckboxesMapping.forEach(m => { - m.default.read.enabled = !this.inheritDefaultsCheckbox.checked; - m.default.write.enabled = !this.inheritDefaultsCheckbox.checked; - m.default.execute.enabled = !this.inheritDefaultsCheckbox.checked; - if (this.inheritDefaultsCheckbox.checked) { - m.model.removePermission(AclEntryScope.default); - m.default.read.checked = false; - m.default.write.checked = false; - m.default.execute.checked = false; - } else { - // Default to the access settings - this is what HDFS does if you don't - // specify the complete set of default ACLs for owner, owning group and other - const accessRead = m.access.read.checked; - const accessWrite = m.access.write.checked; - const accessExecute = m.access.execute.checked; - m.default.read.checked = accessRead; - m.default.write.checked = accessWrite; - m.default.execute.checked = accessExecute; - m.model.addPermission(AclEntryScope.default, new AclEntryPermission(accessRead, accessWrite, accessExecute)); - } - }); - }); - this.defaultSectionComponents.push(this.inheritDefaultsCheckbox); - sectionHeaderContainer.addItem(this.inheritDefaultsCheckbox); - } + const sectionHeaderContainer = this.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', justifyContent: 'flex-end' }).component(); // Access - const accessSectionHeader = modelBuilder.text() + const accessSectionHeader = this.modelBuilder.text() .withProperties({ value: loc.accessHeader, + ariaHidden: true, CSSStyles: { - 'width': `${permissionsReadColumnWidth + permissionsWriteColumnWidth + permissionsExecuteColumnWidth}px`, - 'min-width': `${permissionsReadColumnWidth + permissionsWriteColumnWidth + permissionsExecuteColumnWidth}px`, + // This covers 3 checkbox columns + 'width': `${permissionsCheckboxColumnWidth * 3}px`, + 'min-width': `${permissionsCheckboxColumnWidth * 3}px`, ...cssStyles.permissionsTableHeaderCss } }) .component(); sectionHeaderContainer.addItem(accessSectionHeader, { flex: '0 0 auto' }); - // Default - const defaultSectionHeader = modelBuilder.text() - .withProperties({ - value: loc.defaultHeader, - CSSStyles: { - 'width': `${permissionsReadColumnWidth + permissionsWriteColumnWidth + permissionsExecuteColumnWidth}px`, - 'min-width': `${permissionsReadColumnWidth + permissionsWriteColumnWidth + permissionsExecuteColumnWidth}px`, - ...cssStyles.permissionsTableHeaderCss - } - }) - .component(); - sectionHeaderContainer.addItem(defaultSectionHeader, { flex: '0 0 auto' }); - this.defaultSectionComponents.push(defaultSectionHeader); + // Only show default section for directories + if (this.hdfsModel.fileStatus.type === HdfsFileType.Directory) { + // Middle spacer + const middleSpacer = this.modelBuilder.text().withProperties({ CSSStyles: { 'width': `${middleSpacerWidth}px`, 'min-width': `${middleSpacerWidth}px` } }).component(); + sectionHeaderContainer.addItem(middleSpacer, { flex: '0 0 auto' }); - // Delete - just used as a spacer - const deleteSectionHeader = modelBuilder.text().component(); - sectionHeaderContainer.addItem(deleteSectionHeader, { CSSStyles: { 'width': `${permissionsDeleteColumnWidth}px`, 'min-width': `${permissionsDeleteColumnWidth}px` } }); - - rowsContainer.addItem(sectionHeaderContainer); - - // Table headers - const headerRowContainer = modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component(); - - if (includeStickyAndInherit) { - // Sticky - this.stickyCheckbox = modelBuilder.checkBox() - .withProperties({ - width: checkboxSize, - height: checkboxSize, - checked: false, // Will be set when we get the model update - label: loc.stickyLabel + // Default + const defaultSectionHeader = this.modelBuilder.text() + .withProperties({ + value: loc.defaultHeader, + ariaHidden: true, + CSSStyles: { + // This covers 3 checkbox columns + 'width': `${permissionsCheckboxColumnWidth * 3}px`, + 'min-width': `${permissionsCheckboxColumnWidth * 3}px`, + ...cssStyles.permissionsTableHeaderCss + } }) .component(); - this.stickyCheckbox.onChanged(() => { - this.hdfsModel.permissionStatus.stickyBit = this.stickyCheckbox.checked; - }); - headerRowContainer.addItem(this.stickyCheckbox); + sectionHeaderContainer.addItem(defaultSectionHeader, { flex: '0 0 auto' }); } - // Name - const nameCell = modelBuilder.text().withProperties({ value: nameColumnText }).component(); - headerRowContainer.addItem(nameCell, { flex: '1 1 auto', CSSStyles: { 'width': `${permissionsNameColumnWidth}px`, 'min-width': `${permissionsNameColumnWidth}px`, ...cssStyles.tableHeaderCss } }); + // Right spacer + const rightSpacer = this.modelBuilder.text().withProperties({ CSSStyles: { 'width': `${rightSpacerWidth}px`, 'min-width': `${rightSpacerWidth}px` } }).component(); + sectionHeaderContainer.addItem(rightSpacer, { flex: '0 0 auto' }); - // Access Permissions Group - const accessReadCell = modelBuilder.text().withProperties({ value: loc.readHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component(); - headerRowContainer.addItem(accessReadCell, { CSSStyles: { 'width': `${permissionsReadColumnWidth}px`, 'min-width': `${permissionsReadColumnWidth}px` } }); - const accessWriteCell = modelBuilder.text().withProperties({ value: loc.writeHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component(); - headerRowContainer.addItem(accessWriteCell, { CSSStyles: { 'width': `${permissionsWriteColumnWidth}px`, 'min-width': `${permissionsWriteColumnWidth}px` } }); - const accessExecuteCell = modelBuilder.text().withProperties({ value: loc.executeHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component(); - headerRowContainer.addItem(accessExecuteCell, { CSSStyles: { 'width': `${permissionsExecuteColumnWidth}px`, 'min-width': `${permissionsExecuteColumnWidth}px`, 'margin-right': '5px' } }); - // Default Permissions Group - const defaultPermissionsHeadersContainer = modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component(); - if (includeInherit) { - const inheritCell = modelBuilder.text().withProperties({ value: loc.inheritDefaultsLabel, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component(); - defaultPermissionsHeadersContainer.addItem(inheritCell, { CSSStyles: { 'width': `${permissionsInheritColumnWidth}px`, 'min-width': `${permissionsInheritColumnWidth}px` } }); - } - const defaultReadCell = modelBuilder.text().withProperties({ value: loc.readHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component(); - defaultPermissionsHeadersContainer.addItem(defaultReadCell, { CSSStyles: { 'width': `${permissionsReadColumnWidth}px`, 'min-width': `${permissionsReadColumnWidth}px` } }); - const defaultWriteCell = modelBuilder.text().withProperties({ value: loc.writeHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component(); - defaultPermissionsHeadersContainer.addItem(defaultWriteCell, { CSSStyles: { 'width': `${permissionsWriteColumnWidth}px`, 'min-width': `${permissionsWriteColumnWidth}px` } }); - const defaultExecuteCell = modelBuilder.text().withProperties({ value: loc.executeHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component(); - defaultPermissionsHeadersContainer.addItem(defaultExecuteCell, { CSSStyles: { 'width': `${permissionsExecuteColumnWidth}px`, 'min-width': `${permissionsExecuteColumnWidth}px` } }); - headerRowContainer.addItem(defaultPermissionsHeadersContainer, { flex: '0 0 auto' }); - this.defaultSectionComponents.push(defaultPermissionsHeadersContainer); - - // Delete - const deleteCell = modelBuilder.text().component(); - headerRowContainer.addItem(deleteCell, { CSSStyles: { 'width': `${permissionsDeleteColumnWidth}px`, 'min-width': `${permissionsDeleteColumnWidth}px` } }); - - rowsContainer.addItem(headerRowContainer); - - return rowsContainer; + return sectionHeaderContainer; } } +/** + * Creates a checkbox to be hosted inside of a table cell + * @param builder The ModelBuilder used to create the components + * @param checked Whether the checkbox is initially checked or not + * @param enabled Whether the checkbox is initially enabled or not + * @param containerWidth The width of the container holding the checkbox + * @param containerHeight The height of the container holding the checkbox + * @param ariaLabel The aria label to apply to the checkbox + */ function createCheckbox(builder: azdata.ModelBuilder, checked: boolean, enabled: boolean, containerWidth: number, containerHeight: number, ariaLabel: string): { container: azdata.FlexContainer, checkbox: azdata.CheckBoxComponent } { const checkbox = builder.checkBox() .withProperties({ @@ -556,5 +611,4 @@ function createCheckbox(builder: azdata.ModelBuilder, checked: boolean, enabled: container: container, checkbox: checkbox }; - } diff --git a/extensions/mssql/src/hdfs/ui/uiConstants.ts b/extensions/mssql/src/hdfs/ui/uiConstants.ts index b8e4cf382e..a99d3dbade 100644 --- a/extensions/mssql/src/hdfs/ui/uiConstants.ts +++ b/extensions/mssql/src/hdfs/ui/uiConstants.ts @@ -9,7 +9,5 @@ export namespace cssStyles { export const tableHeaderCss = { 'font-weight': 'bold', 'text-transform': 'uppercase', 'font-size': '10px', 'user-select': 'text' }; export const permissionsTableHeaderCss = { ...tableHeaderCss, 'text-align': 'center' }; export const permissionCheckboxCss = { 'margin-top': '5px', 'margin-left': '13px' }; - // The Named users/groups section potentially has a scrollbar so we shift all the other parts over so the columns still align correctly - export const scrollbarMarginGapCss = { 'margin-right': '17px' }; - export const tableHeaderLayoutCss = { 'padding-left': '10px', 'box-sizing': 'border-box', 'user-select': 'text', ...cssStyles.scrollbarMarginGapCss }; + export const tableHeaderLayoutCss = { 'padding-left': '10px', 'box-sizing': 'border-box', 'user-select': 'text', 'margin-right': '12px' }; } diff --git a/extensions/mssql/src/localizedConstants.ts b/extensions/mssql/src/localizedConstants.ts index 9f107ca244..1333c5630c 100644 --- a/extensions/mssql/src/localizedConstants.ts +++ b/extensions/mssql/src/localizedConstants.ts @@ -15,6 +15,8 @@ export const manageAccessTitle = localize('mssql.manageAccessTitle', "Manage Acc export const locationTitle = localize('mssql.locationTitle', "Location : "); export const permissionsHeader = localize('mssql.permissionsTitle', "Permissions"); export const ownerPostfix = localize('mssql.ownerPostfix', " - Owner"); +export const owner = localize('mssql.owner', "Owner"); +export const group = localize('mssql.group', "Group"); export const owningGroupPostfix = localize('mssql.owningGroupPostfix', " - Owning Group"); export const everyoneName = localize('mssql.everyone', "Everyone else"); export const userLabel = localize('mssql.userLabel', "User"); @@ -31,6 +33,8 @@ export const addUserOrGroupHeader = localize('mssql.addUserOrGroup', "Add User o export const enterNamePlaceholder = localize('mssql.enterNamePlaceholder', "Enter name"); export const addLabel = localize('mssql.addLabel', "Add"); export const namedUsersAndGroupsHeader = localize('mssql.namedUsersAndGroups', "Named Users and Groups"); +export const defaultUserAndGroups = localize('mssql.defaultUserAndGroups', "Default User and Groups"); +export const userOrGroupIcon = localize('mssql.userOrGroupIcon', "User or Group Icon"); export const applyText = localize('mssql.apply', "Apply"); export const applyRecursivelyText = localize('mssql.applyRecursively', "Apply Recursively"); diff --git a/src/sql/azdata.d.ts b/src/sql/azdata.d.ts index 1eb52054c3..fee2c9aab7 100644 --- a/src/sql/azdata.d.ts +++ b/src/sql/azdata.d.ts @@ -3037,8 +3037,17 @@ declare module 'azdata' { } export interface ComponentWithIcon { + /** + * @deprecated This will be moved to `ComponentWithIconProperties` + */ iconPath?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri }; + /** + * @deprecated This will be moved to `ComponentWithIconProperties` + */ iconHeight?: number | string; + /** + * @deprecated This will be moved to `ComponentWithIconProperties` + */ iconWidth?: number | string; } @@ -3244,6 +3253,8 @@ declare module 'azdata' { */ fileContent?: string; /** + * @deprecated This will be moved to `ComponentWithIconProperties` + * * The title for the button. This title will show when hovered over */ title?: string; diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index a17b54fccc..f8324a6d7b 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -162,4 +162,36 @@ declare module 'azdata' { export interface DeclarativeTableProperties extends ComponentProperties { } + + export interface ComponentProperties { + ariaHidden?: boolean; + } + + export interface ComponentWithIconProperties { + /** + * The path for the icon with optional dark-theme away alternative + */ + iconPath?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri }; + /** + * The height of the icon + */ + iconHeight?: number | string; + /** + * The width of the icon + */ + iconWidth?: number | string; + /** + * The title for the icon. This title will show when hovered over + */ + title?: string; + } + + export interface ComponentWithIcon extends ComponentWithIconProperties { + } + + export interface ImageComponent extends ComponentWithIcon { + } + + export interface ImageComponentProperties extends ComponentProperties, ComponentWithIconProperties { + } } diff --git a/src/sql/workbench/api/common/extHostModelView.ts b/src/sql/workbench/api/common/extHostModelView.ts index 04a00dfa97..bdb25c9e23 100644 --- a/src/sql/workbench/api/common/extHostModelView.ts +++ b/src/sql/workbench/api/common/extHostModelView.ts @@ -594,6 +594,14 @@ class ComponentWrapper implements azdata.Component { this.setProperty('ariaSelected', v); } + public get ariaHidden(): boolean { + return this.properties['ariaHidden']; + } + + public set ariaHidden(v: boolean) { + this.setProperty('ariaHidden', v); + } + public get CSSStyles(): { [key: string]: string } { return this.properties['CSSStyles']; } @@ -771,6 +779,13 @@ class ComponentWithIconWrapper extends ComponentWrapper { public set iconWidth(v: string | number) { this.setProperty('iconWidth', v); } + + public get title(): string { + return this.properties['title']; + } + public set title(v: string) { + this.setProperty('title', v); + } } class CardWrapper extends ComponentWrapper implements azdata.CardComponent { @@ -1465,13 +1480,6 @@ class ButtonWrapper extends ComponentWithIconWrapper implements azdata.ButtonCom this.setProperty('label', v); } - public get title(): string { - return this.properties['title']; - } - public set title(v: string) { - this.setProperty('title', v); - } - public get onDidClick(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidClick); return emitter && emitter.event; diff --git a/src/sql/workbench/browser/modelComponents/button.component.ts b/src/sql/workbench/browser/modelComponents/button.component.ts index 2f25d92e40..fad48121f6 100644 --- a/src/sql/workbench/browser/modelComponents/button.component.ts +++ b/src/sql/workbench/browser/modelComponents/button.component.ts @@ -184,12 +184,4 @@ export default class ButtonComponent extends ComponentWithIconBase implements IC private setFileProperties(properties: azdata.ButtonProperties, isFile: boolean): void { properties.isFile = isFile; } - - private get title(): string { - return this.getPropertyOrDefault((props) => props.title, ''); - } - - private set title(newValue: string) { - this.setPropertyFromUI((properties, title) => { properties.title = title; }, newValue); - } } diff --git a/src/sql/workbench/browser/modelComponents/componentBase.ts b/src/sql/workbench/browser/modelComponents/componentBase.ts index 485708d104..3297064a7b 100644 --- a/src/sql/workbench/browser/modelComponents/componentBase.ts +++ b/src/sql/workbench/browser/modelComponents/componentBase.ts @@ -200,6 +200,14 @@ export abstract class ComponentBase extends Disposable implements IComponent, On this.setPropertyFromUI((props, value) => props.ariaSelected = value, newValue); } + public get ariaHidden(): boolean { + return this.getPropertyOrDefault((props) => props.ariaHidden, false); + } + + public set ariaHidden(newValue: boolean) { + this.setPropertyFromUI((props, value) => props.ariaHidden = value, newValue); + } + public get CSSStyles(): { [key: string]: string } { return this.getPropertyOrDefault((props) => props.CSSStyles, {}); } diff --git a/src/sql/workbench/browser/modelComponents/componentWithIconBase.ts b/src/sql/workbench/browser/modelComponents/componentWithIconBase.ts index 309b85ad55..9334f02529 100644 --- a/src/sql/workbench/browser/modelComponents/componentWithIconBase.ts +++ b/src/sql/workbench/browser/modelComponents/componentWithIconBase.ts @@ -48,15 +48,23 @@ export abstract class ComponentWithIconBase extends ComponentBase { } public get iconPath(): string | URI | { light: string | URI; dark: string | URI } { - return this.getPropertyOrDefault((props) => props.iconPath, undefined); + return this.getPropertyOrDefault((props) => props.iconPath, undefined); } public get iconHeight(): number | string { - return this.getPropertyOrDefault((props) => props.iconHeight, '50px'); + return this.getPropertyOrDefault((props) => props.iconHeight, '50px'); } public get iconWidth(): number | string { - return this.getPropertyOrDefault((props) => props.iconWidth, '50px'); + return this.getPropertyOrDefault((props) => props.iconWidth, '50px'); + } + + public get title(): string { + return this.getPropertyOrDefault((props) => props.title, ''); + } + + public set title(newTitle: string) { + this.setPropertyFromUI((properties, title) => { properties.title = title; }, newTitle); } ngOnDestroy(): void { diff --git a/src/sql/workbench/browser/modelComponents/declarativeTable.component.ts b/src/sql/workbench/browser/modelComponents/declarativeTable.component.ts index f45045aa63..edb81433e1 100644 --- a/src/sql/workbench/browser/modelComponents/declarativeTable.component.ts +++ b/src/sql/workbench/browser/modelComponents/declarativeTable.component.ts @@ -31,7 +31,7 @@ export enum DeclarativeDataType { - + @@ -159,8 +159,8 @@ export default class DeclarativeTableComponent extends ComponentBase implements return this.columns[colIdx].valueType === DeclarativeDataType.component; } - public getColumnWidth(colIdx: number): string { - let column: azdata.DeclarativeTableColumn = this.columns[colIdx]; + public getColumnWidth(col: number | azdata.DeclarativeTableColumn): string { + let column = typeof col === 'number' ? this.columns[col] : col; return this.convertSize(column.width, '30px'); } diff --git a/src/sql/workbench/browser/modelComponents/image.component.ts b/src/sql/workbench/browser/modelComponents/image.component.ts index 663f7deb5f..3d0f73b2d2 100644 --- a/src/sql/workbench/browser/modelComponents/image.component.ts +++ b/src/sql/workbench/browser/modelComponents/image.component.ts @@ -9,15 +9,15 @@ import { } from '@angular/core'; import * as DOM from 'vs/base/browser/dom'; -import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/browser/modelComponents/interfaces'; +import { IComponent, IComponentDescriptor, IModelStore, ITitledComponent } from 'sql/workbench/browser/modelComponents/interfaces'; import { ComponentWithIconBase } from 'sql/workbench/browser/modelComponents/componentWithIconBase'; @Component({ selector: 'modelview-image', template: ` -
` +
` }) -export default class ImageComponent extends ComponentWithIconBase implements IComponent, OnDestroy, AfterViewInit { +export default class ImageComponent extends ComponentWithIconBase implements ITitledComponent, IComponent, OnDestroy, AfterViewInit { @Input() descriptor: IComponentDescriptor; @Input() modelStore: IModelStore; @ViewChild('imageContainer', { read: ElementRef }) imageContainer: ElementRef; diff --git a/src/sql/workbench/browser/modelComponents/text.component.ts b/src/sql/workbench/browser/modelComponents/text.component.ts index f5e5939178..e74d91695c 100644 --- a/src/sql/workbench/browser/modelComponents/text.component.ts +++ b/src/sql/workbench/browser/modelComponents/text.component.ts @@ -19,14 +19,14 @@ import { TitledComponent } from 'sql/workbench/browser/modelComponents/titledCom selector: 'modelview-text', template: `
-

+

*

-

+

` }) export default class TextComponent extends TitledComponent implements IComponent, OnDestroy, AfterViewInit { diff --git a/src/sql/workbench/browser/modelComponents/titledComponent.ts b/src/sql/workbench/browser/modelComponents/titledComponent.ts index 328c140250..01ce1a3e0e 100644 --- a/src/sql/workbench/browser/modelComponents/titledComponent.ts +++ b/src/sql/workbench/browser/modelComponents/titledComponent.ts @@ -20,10 +20,10 @@ export abstract class TitledComponent extends ComponentBase implements ITitledCo } public get title(): string { - return this.getPropertyOrDefault((props) => props.title, ''); + return this.getPropertyOrDefault((props) => props.title, ''); } public set title(newTitle: string) { - this.setPropertyFromUI((properties, title) => { properties.title = title; }, newTitle); + this.setPropertyFromUI((properties, title) => { properties.title = title; }, newTitle); } }
{{column.displayName}}{{column.displayName}}