More updates to HDFS Manage Access dialog (#7611)

* Add display property to ModelView components

* Update DisplayType property in sqlops as well

* More updates to HDFS Manage Access dialog

* More updates to HDFS Manage Access dialog
This commit is contained in:
Charles Gagnon
2019-10-10 10:57:38 -07:00
committed by GitHub
parent 93c9426f25
commit 543e3e2c09
17 changed files with 569 additions and 235 deletions

View File

@@ -3,38 +3,44 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IconPathHelper, IconPath } from '../iconHelper';
/**
* The parsed result from calling getAclStatus on the controller
* The permission status of an HDFS path - this consists of :
* - The sticky bit for that path
* - The permission bits for the owner, group and other
* - (Optional) Set of additional ACL entries on this path
*/
export interface IAclStatus {
export class PermissionStatus {
/**
* The ACL entries defined for the object
*/
entries: AclEntry[];
/**
* The ACL entry object for the owner permissions
*/
owner: AclEntry;
/**
* The ACL entry object for the group permissions
*/
group: AclEntry;
/**
* The ACL entry object for the other permissions
*/
other: AclEntry;
/**
* The sticky bit status for the object. If true the owner/root are
*
* @param owner The ACL entry object for the owner permissions
* @param group The ACL entry object for the group permissions
* @param other The ACL entry object for the other permissions
* @param stickyBit The sticky bit status for the object. If true the owner/root are
* the only ones who can delete the resource or its contents (if a folder)
* @param aclEntries The ACL entries defined for the object
*/
stickyBit: boolean;
constructor(public owner: AclEntry, public group: AclEntry, public other: AclEntry, public stickyBit: boolean, public aclEntries: AclEntry[]) { }
/**
* The permission octal for the path in the form [#]### with each # mapping to :
* 0 (optional) - The sticky bit (1 or 0)
* 1 - The owner permission digit
* 2 - The group permission digit
* 3 - The other permission digit
* @see AclEntryPermission for more information on the permission digits
*/
public get permissionOctal(): string {
return `${this.stickyBit ? '1' : ''}${this.owner.permissionDigit}${this.group.permissionDigit}${this.other.permissionDigit}`;
}
}
/**
* The type of an ACL entry. Corresponds to the first (or second if a scope is present) field of
* an ACL entry - e.g. user:bob:rwx (user) or default:group::r-- (group)
*/
export enum AclEntryType {
export enum AclType {
/**
* An ACL entry applied to a specific user.
*/
@@ -58,7 +64,7 @@ export enum AclEntryType {
* Typically this value is represented as a 3 digit octal - e.g. 740 - where the first digit is the owner, the second
* the group and the third other. @see parseAclPermissionFromOctal
*/
export enum AclPermissionType {
export enum PermissionType {
owner = 'owner',
group = 'group',
other = 'other'
@@ -92,6 +98,14 @@ export class AclEntryPermission {
public toString() {
return `${this.read ? 'r' : '-'}${this.write ? 'w' : '-'}${this.execute ? 'x' : '-'}`;
}
/**
* Gets the digit for a permission octal for this permission. This digit is a value
* between 0 and 7 inclusive, which is a bitwise OR the permission flags (r/w/x).
*/
public get permissionDigit(): number {
return (this.read ? 4 : 0) + (this.write ? 2 : 0) + (this.execute ? 1 : 0);
}
}
/**
@@ -123,12 +137,19 @@ function parseAclPermission(permissionString: string): AclEntryPermission {
export class AclEntry {
constructor(
public readonly scope: AclEntryScope,
public readonly type: AclEntryType | AclPermissionType,
public readonly type: AclType | PermissionType,
public readonly name: string,
public readonly displayName: string,
public readonly permission: AclEntryPermission,
) { }
/**
* Gets the octal number representing the permission for this entry. This digit is a value
* between 0 and 7 inclusive, which is a bitwise OR the permission flags (r/w/x).
*/
public get permissionDigit(): number {
return this.permission.permissionDigit;
}
/**
* Returns the string representation of the ACL Entry in the form [SCOPE:]TYPE:NAME:PERMISSION.
* Note that SCOPE is only displayed if it's default - access is implied if there is no scope
@@ -163,22 +184,22 @@ export class AclEntry {
* Maps the possible entry types into their corresponding values for using in an ACL string
* @param type The type to convert
*/
function getAclEntryType(type: AclEntryType | AclPermissionType): AclEntryType {
function getAclEntryType(type: AclType | PermissionType): AclType {
// We only need to map AclPermissionType - AclEntryType is already the
// correct values we're mapping to.
if (type in AclPermissionType) {
if (type in PermissionType) {
switch (type) {
case AclPermissionType.owner:
return AclEntryType.user;
case AclPermissionType.group:
return AclEntryType.group;
case AclPermissionType.other:
return AclEntryType.other;
case PermissionType.owner:
return AclType.user;
case PermissionType.group:
return AclType.group;
case PermissionType.other:
return AclType.other;
default:
throw new Error(`Unknown AclPermissionType : ${type}`);
}
}
return <AclEntryType>type;
return <AclType>type;
}
/**
@@ -213,19 +234,19 @@ export function parseAclEntry(aclString: string): AclEntry {
const parts: string[] = aclString.split(':');
let i = 0;
const scope: AclEntryScope = parts.length === 4 && parts[i++] === 'default' ? AclEntryScope.default : AclEntryScope.access;
let type: AclEntryType;
let type: AclType;
switch (parts[i++]) {
case 'user':
type = AclEntryType.user;
type = AclType.user;
break;
case 'group':
type = AclEntryType.group;
type = AclType.group;
break;
case 'mask':
type = AclEntryType.mask;
type = AclType.mask;
break;
case 'other':
type = AclEntryType.other;
type = AclType.other;
break;
default:
throw new Error(`Unknown ACL Entry type ${parts[i - 1]}`);
@@ -269,3 +290,16 @@ export function parseAclPermissionFromOctal(octal: string): { sticky: boolean, o
other: new AclEntryPermission((otherPermissionDigit & 4) === 4, (otherPermissionDigit & 2) === 2, (otherPermissionDigit & 1) === 1)
};
}
export function getImageForType(type: AclType | PermissionType): IconPath {
switch (type) {
case AclType.user:
case PermissionType.owner:
return IconPathHelper.user;
case AclType.group:
case PermissionType.group:
case PermissionType.other:
return IconPathHelper.group;
}
return { dark: '', light: '' };
}

View File

@@ -0,0 +1,85 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export enum HdfsFileType {
File = 'File',
Directory = 'Directory',
Symlink = 'Symlink'
}
export class FileStatus {
/**
*
* @param owner The ACL entry object for the owner permissions
* @param group The ACL entry object for the group permissions
* @param other The ACL entry object for the other permissions
* @param stickyBit The sticky bit status for the object. If true the owner/root are
* the only ones who can delete the resource or its contents (if a folder)
* @param aclEntries The ACL entries defined for the object
*/
constructor(
/**
* Access time for the file
*/
public readonly accessTime: string,
/**
* The block size of a file.
*/
public readonly blockSize: string,
/**
* The group owner.
*/
public readonly group: string,
/**
* The number of bytes in a file. (0 for directories)
*/
public readonly length: string,
/**
* The modification time.
*/
public readonly modificationTime: string,
/**
* The user who is the owner.
*/
public readonly owner: string,
/**
* The path suffix.
*/
public readonly pathSuffix: string,
/**
* The permission represented as a octal string.
*/
public readonly permission: string,
/**
* The number of replication of a file.
*/
public readonly replication: string,
/**
* Whether a directory is snapshot enabled or not
*/
public readonly snapshotEnabled: string,
/**
* The type of the path object.
*/
public readonly type: HdfsFileType
) { }
}
/**
* Parses a fileType string into the corresponding @see HdfsFileType
* @param fileType The fileType string to parse
*/
export function parseHdfsFileType(fileType: string): HdfsFileType {
switch (fileType.toLowerCase()) {
case 'file':
return HdfsFileType.File;
case 'directory':
return HdfsFileType.Directory;
case 'symlink':
return HdfsFileType.Symlink;
default:
throw new Error(`Unknown HdfsFileType '${fileType}'`);
}
}

View File

@@ -5,23 +5,29 @@
import * as vscode from 'vscode';
import { IFileSource } from '../objectExplorerNodeProvider/fileSources';
import { IAclStatus, AclEntry, AclEntryScope, AclEntryType, AclEntryPermission } from './aclEntry';
import { PermissionStatus, AclEntry, AclEntryScope, AclType, AclEntryPermission } from './aclEntry';
import { FileStatus } from './fileStatus';
/**
* Model for storing the state of a specified file/folder in HDFS
*/
export class HdfsModel {
private readonly _onAclStatusUpdated = new vscode.EventEmitter<IAclStatus>();
private readonly _onPermissionStatusUpdated = new vscode.EventEmitter<PermissionStatus>();
/**
* Event that's fired anytime changes are made by the model to the ACLStatus
* Event that's fired anytime changes are made by the model to the @see PermissionStatus
*/
public onAclStatusUpdated = this._onAclStatusUpdated.event;
public onPermissionStatusUpdated = this._onPermissionStatusUpdated.event;
/**
* The ACL status of the file/folder
* The @see PermissionStatus of the file/folder
*/
public aclStatus: IAclStatus;
public permissionStatus: PermissionStatus;
/**
* The @see FileStatus of the file/folder
*/
public fileStatus: FileStatus;
constructor(private fileSource: IFileSource, private path: string) {
this.refresh();
@@ -31,8 +37,10 @@ export class HdfsModel {
* Refresh the ACL status with the current values on HDFS
*/
public async refresh(): Promise<void> {
this.aclStatus = await this.fileSource.getAclStatus(this.path);
this._onAclStatusUpdated.fire(this.aclStatus);
[this.permissionStatus, this.fileStatus] = await Promise.all([
this.fileSource.getAclStatus(this.path),
this.fileSource.getFileStatus(this.path)]);
this._onPermissionStatusUpdated.fire(this.permissionStatus);
}
/**
@@ -41,18 +49,18 @@ export class HdfsModel {
* @param name The name of the ACL Entry
* @param type The type of ACL to create
*/
public createAndAddAclEntry(name: string, type: AclEntryType): void {
if (!this.aclStatus) {
public createAndAddAclEntry(name: string, type: AclType): void {
if (!this.permissionStatus) {
return;
}
const newEntry = new AclEntry(AclEntryScope.access, type, name, name, new AclEntryPermission(true, true, true));
// Don't add duplicates. This also checks the owner, group and other items
if ([this.aclStatus.owner, this.aclStatus.group, this.aclStatus.other].concat(this.aclStatus.entries).find(entry => entry.isEqual(newEntry))) {
if ([this.permissionStatus.owner, this.permissionStatus.group, this.permissionStatus.other].concat(this.permissionStatus.aclEntries).find(entry => entry.isEqual(newEntry))) {
return;
}
this.aclStatus.entries.push(newEntry);
this._onAclStatusUpdated.fire(this.aclStatus);
this.permissionStatus.aclEntries.push(newEntry);
this._onPermissionStatusUpdated.fire(this.permissionStatus);
}
/**
@@ -60,8 +68,8 @@ export class HdfsModel {
* @param entryToDelete The entry to delete
*/
public deleteAclEntry(entryToDelete: AclEntry): void {
this.aclStatus.entries = this.aclStatus.entries.filter(entry => !entry.isEqual(entryToDelete));
this._onAclStatusUpdated.fire(this.aclStatus);
this.permissionStatus.aclEntries = this.permissionStatus.aclEntries.filter(entry => !entry.isEqual(entryToDelete));
this._onPermissionStatusUpdated.fire(this.permissionStatus);
}
@@ -70,8 +78,10 @@ export class HdfsModel {
* permissions that shouldn't change need to still exist and have the same values.
* @param recursive Whether to apply the changes recursively (to all sub-folders and files)
*/
public apply(recursive: boolean = false): Promise<void> {
public apply(recursive: boolean = false): Promise<any> {
// TODO Apply recursive
return this.fileSource.setAcl(this.path, this.aclStatus.owner, this.aclStatus.group, this.aclStatus.other, this.aclStatus.entries);
return Promise.all([
this.fileSource.setAcl(this.path, this.permissionStatus.owner, this.permissionStatus.group, this.permissionStatus.other, this.permissionStatus.aclEntries),
this.fileSource.setPermission(this.path, this.permissionStatus)]);
}
}

View File

@@ -6,13 +6,15 @@
import * as azdata from 'azdata';
import { HdfsModel } from '../hdfsModel';
import { IFileSource } from '../../objectExplorerNodeProvider/fileSources';
import { IAclStatus, AclEntry, AclEntryType } from '../../hdfs/aclEntry';
import { PermissionStatus, AclEntry, AclType, getImageForType } from '../../hdfs/aclEntry';
import { cssStyles } from './uiConstants';
import * as loc from '../../localizedConstants';
import { HdfsError } from '../webhdfs';
import { ApiWrapper } from '../../apiWrapper';
import { IconPathHelper } from '../../iconHelper';
import { HdfsFileType } from '../fileStatus';
const permissionsTypeIconColumnWidth = 35;
const permissionsNameColumnWidth = 250;
const permissionsStickyColumnWidth = 50;
const permissionsReadColumnWidth = 50;
@@ -36,11 +38,13 @@ export class ManageAccessDialog {
private addUserOrGroupInput: azdata.InputBoxComponent;
private dialog: azdata.window.Dialog;
private addUserOrGroupSelectedType: AclEntryType;
private defaultSectionComponents: azdata.Component[] = [];
private addUserOrGroupSelectedType: AclType;
constructor(private hdfsPath: string, private fileSource: IFileSource, private readonly apiWrapper: ApiWrapper) {
this.hdfsModel = new HdfsModel(this.fileSource, this.hdfsPath);
this.hdfsModel.onAclStatusUpdated(aclStatus => this.handleAclStatusUpdated(aclStatus));
this.hdfsModel.onPermissionStatusUpdated(permissionStatus => this.handlePermissionStatusUpdated(permissionStatus));
}
public openDialog(): void {
@@ -123,7 +127,7 @@ export class ManageAccessDialog {
// = Owners permissions section =
// ==============================
const ownersPermissionsHeaderRow = createPermissionsHeaderRow(modelView.modelBuilder, loc.ownersHeader, true);
const ownersPermissionsHeaderRow = this.createPermissionsHeaderRow(modelView.modelBuilder, loc.ownersHeader, true);
contentContainer.addItem(ownersPermissionsHeaderRow, { CSSStyles: { ...cssStyles.tableHeaderLayoutCss } });
// Empty initially - this is going to eventually be populated with the owner/owning group permissions
@@ -155,10 +159,10 @@ export class ManageAccessDialog {
const typeContainer = modelView.modelBuilder.flexContainer().withProperties({ flexFlow: 'row' }).component();
const aclEntryTypeGroup = 'aclEntryType';
const userTypeButton = this.createRadioButton(modelView.modelBuilder, loc.userLabel, aclEntryTypeGroup, AclEntryType.user);
const groupTypeButton = this.createRadioButton(modelView.modelBuilder, loc.groupLabel, aclEntryTypeGroup, AclEntryType.group);
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 = AclEntryType.user;
this.addUserOrGroupSelectedType = AclType.user;
typeContainer.addItems([userTypeButton, groupTypeButton], { flex: '0 0 auto' });
contentContainer.addItem(typeContainer, { flex: '0 0 auto' });
@@ -199,7 +203,7 @@ export class ManageAccessDialog {
// = Named Users and Groups permissions header row =
// =================================================
const namedUsersAndGroupsPermissionsHeaderRow = createPermissionsHeaderRow(modelView.modelBuilder, loc.namedUsersAndGroupsHeader, false);
const namedUsersAndGroupsPermissionsHeaderRow = this.createPermissionsHeaderRow(modelView.modelBuilder, loc.namedUsersAndGroupsHeader, false);
contentContainer.addItem(namedUsersAndGroupsPermissionsHeaderRow, { CSSStyles: { ...cssStyles.tableHeaderLayoutCss } });
// Empty initially - this is eventually going to be populated with the ACL entries set for this path
@@ -209,7 +213,7 @@ export class ManageAccessDialog {
contentContainer.addItem(this.namedUsersAndGroupsPermissionsContainer, { flex: '1', CSSStyles: { 'overflow': 'scroll', 'min-height': '200px' } });
this.viewInitialized = true;
this.handleAclStatusUpdated(this.hdfsModel.aclStatus);
this.handlePermissionStatusUpdated(this.hdfsModel.permissionStatus);
await modelView.initializeModel(this.rootLoadingComponent);
});
this.dialog.content = [tab];
@@ -218,25 +222,28 @@ export class ManageAccessDialog {
azdata.window.openDialog(this.dialog);
}
private handleAclStatusUpdated(aclStatus: IAclStatus): void {
if (!aclStatus || !this.viewInitialized) {
private handlePermissionStatusUpdated(permissionStatus: PermissionStatus): void {
if (!permissionStatus || !this.viewInitialized) {
return;
}
// 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');
// Owners
const ownerPermissionsRow = this.createOwnerPermissionsRow(this.modelBuilder, aclStatus.stickyBit, aclStatus.owner);
const owningGroupPermissionsRow = this.createPermissionsRow(this.modelBuilder, aclStatus.group, false);
const ownerPermissionsRow = this.createOwnerPermissionsRow(this.modelBuilder, permissionStatus);
const owningGroupPermissionsRow = this.createPermissionsRow(this.modelBuilder, permissionStatus.group, /*includeDelete*/false);
this.ownersPermissionsContainer.clearItems();
this.ownersPermissionsContainer.addItems([ownerPermissionsRow, owningGroupPermissionsRow], { CSSStyles: { 'border-bottom': cssStyles.tableBorderCss, 'border-top': cssStyles.tableBorderCss, 'margin-right': '14px' } });
// Others
const otherPermissionsRow = this.createPermissionsRow(this.modelBuilder, aclStatus.other, false);
const otherPermissionsRow = this.createPermissionsRow(this.modelBuilder, permissionStatus.other, false);
this.othersPermissionsContainer.clearItems();
this.othersPermissionsContainer.addItem(otherPermissionsRow, { CSSStyles: { 'border-bottom': cssStyles.tableBorderCss, 'border-top': cssStyles.tableBorderCss, 'margin-right': '14px' } });
this.namedUsersAndGroupsPermissionsContainer.clearItems();
// Named users and groups
aclStatus.entries.forEach(entry => {
permissionStatus.aclEntries.forEach(entry => {
const namedEntryRow = this.createPermissionsRow(this.modelBuilder, entry, true);
this.namedUsersAndGroupsPermissionsContainer.addItem(namedEntryRow, { CSSStyles: { 'border-bottom': cssStyles.tableBorderCss, 'border-top': cssStyles.tableBorderCss } });
});
@@ -244,7 +251,7 @@ export class ManageAccessDialog {
this.rootLoadingComponent.loading = false;
}
private createRadioButton(modelBuilder: azdata.ModelBuilder, label: string, name: string, aclEntryType: AclEntryType): azdata.RadioButtonComponent {
private createRadioButton(modelBuilder: azdata.ModelBuilder, label: string, name: string, aclEntryType: AclType): azdata.RadioButtonComponent {
const button = modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({ label: label, name: name }).component();
button.onDidClick(() => {
this.addUserOrGroupSelectedType = aclEntryType;
@@ -252,16 +259,33 @@ export class ManageAccessDialog {
return button;
}
private createOwnerPermissionsRow(builder: azdata.ModelBuilder, sticky: boolean, entry: AclEntry): azdata.FlexContainer {
const row = this.createPermissionsRow(builder, entry, false);
const stickyComponents = createCheckbox(builder, sticky, permissionsReadColumnWidth, permissionsRowHeight);
private createOwnerPermissionsRow(builder: azdata.ModelBuilder, permissionStatus: PermissionStatus): azdata.FlexContainer {
const row = this.createPermissionsRow(builder, permissionStatus.owner, false);
const stickyComponents = createCheckbox(builder, permissionStatus.stickyBit, permissionsReadColumnWidth, permissionsRowHeight);
stickyComponents.checkbox.onChanged(() => {
permissionStatus.stickyBit = stickyComponents.checkbox.checked;
});
// Insert after name item but before other checkboxes
row.insertItem(stickyComponents.container, 1, { flex: '0 0 auto' });
row.insertItem(stickyComponents.container, 2, { flex: '0 0 auto' });
return row;
}
private createPermissionsRow(builder: azdata.ModelBuilder, entry: AclEntry, includeDelete: boolean): azdata.FlexContainer {
const rowContainer = builder.flexContainer().withLayout({ flexFlow: 'row', height: permissionsRowHeight }).component();
// Icon
const iconCell = builder.image()
.withProperties<azdata.ImageComponentProperties>({
iconPath: getImageForType(entry.type),
width: permissionsTypeIconColumnWidth,
height: permissionsRowHeight,
iconWidth: 20,
iconHeight: 20
})
.component();
rowContainer.addItem(iconCell, { flex: '0 0 auto' });
// Name
const nameCell = builder.text().withProperties({ value: entry.displayName }).component();
rowContainer.addItem(nameCell);
@@ -281,31 +305,35 @@ export class ManageAccessDialog {
// Access - Execute
const accessExecuteComponents = createCheckbox(builder, entry.permission.execute, permissionsExecuteColumnWidth, permissionsRowHeight);
rowContainer.addItem(accessExecuteComponents.container, { flex: '0 0 auto', CSSStyles: { 'border-right': cssStyles.tableBorderCss } });
rowContainer.addItem(accessExecuteComponents.container, { flex: '0 0 auto', CSSStyles: { 'border-right': this.hdfsModel.fileStatus.type === HdfsFileType.Directory ? cssStyles.tableBorderCss : '' } });
accessExecuteComponents.checkbox.onChanged(() => {
entry.permission.execute = accessExecuteComponents.checkbox.checked;
});
// Default - Read
const defaultReadComponents = createCheckbox(builder, false, permissionsReadColumnWidth, permissionsRowHeight);
rowContainer.addItem(defaultReadComponents.container, { flex: '0 0 auto' });
defaultReadComponents.checkbox.onChanged(() => {
// entry.permission.read = defaultReadComponents.checkbox.checked; TODO hook up default logic
});
// Only directories can set ACL defaults so we hide the column for non-directories
if (this.hdfsModel.fileStatus.type === HdfsFileType.Directory) {
// Default - Read
const defaultReadComponents = createCheckbox(builder, false, permissionsReadColumnWidth, permissionsRowHeight);
rowContainer.addItem(defaultReadComponents.container, { flex: '0 0 auto' });
defaultReadComponents.checkbox.onChanged(() => {
// entry.permission.read = defaultReadComponents.checkbox.checked; TODO hook up default logic
});
// Default - Write
const defaultWriteComponents = createCheckbox(builder, false, permissionsWriteColumnWidth, permissionsRowHeight);
rowContainer.addItem(defaultWriteComponents.container, { flex: '0 0 auto' });
accessReadComponents.checkbox.onChanged(() => {
// entry.permission.write = accessReadComponents.checkbox.checked; TODO hook up default logic
});
// Default - Write
const defaultWriteComponents = createCheckbox(builder, false, permissionsWriteColumnWidth, permissionsRowHeight);
rowContainer.addItem(defaultWriteComponents.container, { flex: '0 0 auto' });
accessReadComponents.checkbox.onChanged(() => {
// entry.permission.write = accessReadComponents.checkbox.checked; TODO hook up default logic
});
// Default - Execute
const defaultExecuteComponents = createCheckbox(builder, false, permissionsExecuteColumnWidth, permissionsRowHeight);
rowContainer.addItem(defaultExecuteComponents.container, { flex: '0 0 auto' });
accessReadComponents.checkbox.onChanged(() => {
// entry.permission.execute = accessReadComponents.checkbox.checked; TODO hook up default logic
});
}
// Default - Execute
const defaultExecuteComponents = createCheckbox(builder, false, permissionsExecuteColumnWidth, permissionsRowHeight);
rowContainer.addItem(defaultExecuteComponents.container, { flex: '0 0 auto' });
accessReadComponents.checkbox.onChanged(() => {
// entry.permission.execute = accessReadComponents.checkbox.checked; TODO hook up default logic
});
const deleteContainer = builder.flexContainer().withLayout({ width: permissionsDeleteColumnWidth, height: permissionsRowHeight }).component();
@@ -327,60 +355,90 @@ export class ManageAccessDialog {
return rowContainer;
}
}
/**
* Creates the header row for the permissions tables. This contains headers for the name, optional sticky and then read/write/execute for both
* access and default sections.
* @param modelBuilder The builder used to create the model components
* @param nameColumnText The text to display for the name column
* @param includeSticky Whether to include the sticky header
*/
function createPermissionsHeaderRow(modelBuilder: azdata.ModelBuilder, nameColumnText: string, includeSticky: boolean): azdata.FlexContainer {
const rowsContainer = modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
/**
* Creates the header row for the permissions tables. This contains headers for the name, optional sticky and then read/write/execute for both
* access and default sections.
* @param modelBuilder The builder used to create the model components
* @param nameColumnText The text to display for the name column
* @param includeSticky Whether to include the sticky header
*/
private createPermissionsHeaderRow(modelBuilder: azdata.ModelBuilder, nameColumnText: string, includeSticky: boolean): azdata.FlexContainer {
const rowsContainer = modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
// Section Headers
const sectionHeaderContainer = modelBuilder.flexContainer().withLayout({ flexFlow: 'row', justifyContent: 'flex-end' }).component();
const accessSectionHeader = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.accessHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component();
sectionHeaderContainer.addItem(accessSectionHeader, { CSSStyles: { 'width': `${permissionsReadColumnWidth + permissionsWriteColumnWidth + permissionsExecuteColumnWidth}px`, 'min-width': `${permissionsReadColumnWidth + permissionsWriteColumnWidth + permissionsExecuteColumnWidth}px` } });
const defaultSectionHeader = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.defaultHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component();
sectionHeaderContainer.addItem(defaultSectionHeader, { CSSStyles: { 'width': `${permissionsReadColumnWidth + permissionsWriteColumnWidth + permissionsExecuteColumnWidth}px`, 'min-width': `${permissionsReadColumnWidth + permissionsWriteColumnWidth + permissionsExecuteColumnWidth}px` } });
// Delete - just used as a spacer
const deleteSectionHeader = modelBuilder.text().component();
sectionHeaderContainer.addItem(deleteSectionHeader, { CSSStyles: { 'width': `${permissionsDeleteColumnWidth}px`, 'min-width': `${permissionsDeleteColumnWidth}px` } });
// Section Headers
const sectionHeaderContainer = modelBuilder.flexContainer().withLayout({ flexFlow: 'row', justifyContent: 'flex-end' }).component();
const accessSectionHeader = modelBuilder.text()
.withProperties<azdata.TextComponentProperties>({
value: loc.accessHeader,
CSSStyles: {
'width': `${permissionsReadColumnWidth + permissionsWriteColumnWidth + permissionsExecuteColumnWidth}px`,
'min-width': `${permissionsReadColumnWidth + permissionsWriteColumnWidth + permissionsExecuteColumnWidth}px`,
...cssStyles.permissionsTableHeaderCss
}
})
.component();
rowsContainer.addItem(sectionHeaderContainer);
sectionHeaderContainer.addItem(accessSectionHeader, { flex: '0 0 auto' });
const defaultSectionHeader = modelBuilder.text()
.withProperties<azdata.TextComponentProperties>({
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);
// Delete - just used as a spacer
const deleteSectionHeader = modelBuilder.text().component();
sectionHeaderContainer.addItem(deleteSectionHeader, { CSSStyles: { 'width': `${permissionsDeleteColumnWidth}px`, 'min-width': `${permissionsDeleteColumnWidth}px` } });
// Table headers
const headerRowContainer = modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component();
const ownersCell = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: nameColumnText }).component();
headerRowContainer.addItem(ownersCell, { flex: '1 1 auto', CSSStyles: { 'width': `${permissionsNameColumnWidth}px`, 'min-width': `${permissionsNameColumnWidth}px`, ...cssStyles.tableHeaderCss } });
if (includeSticky) {
const stickyCell = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.stickyHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component();
headerRowContainer.addItem(stickyCell, { CSSStyles: { 'width': `${permissionsStickyColumnWidth}px`, 'min-width': `${permissionsStickyColumnWidth}px` } });
rowsContainer.addItem(sectionHeaderContainer);
// Table headers
const headerRowContainer = modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component();
// Icon (spacer, no text)
const typeIconCell = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ CSSStyles: { 'width': `${permissionsTypeIconColumnWidth}px`, 'min-width': `${permissionsTypeIconColumnWidth}px` } }).component();
headerRowContainer.addItem(typeIconCell, { flex: '0 0 auto' });
// Name
const nameCell = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: nameColumnText }).component();
headerRowContainer.addItem(nameCell, { flex: '1 1 auto', CSSStyles: { 'width': `${permissionsNameColumnWidth}px`, 'min-width': `${permissionsNameColumnWidth}px`, ...cssStyles.tableHeaderCss } });
if (includeSticky) {
const stickyCell = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.stickyHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component();
headerRowContainer.addItem(stickyCell, { CSSStyles: { 'width': `${permissionsStickyColumnWidth}px`, 'min-width': `${permissionsStickyColumnWidth}px` } });
}
// Access Permissions Group
const accessReadCell = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.readHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component();
headerRowContainer.addItem(accessReadCell, { CSSStyles: { 'width': `${permissionsReadColumnWidth}px`, 'min-width': `${permissionsReadColumnWidth}px` } });
const accessWriteCell = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.writeHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component();
headerRowContainer.addItem(accessWriteCell, { CSSStyles: { 'width': `${permissionsWriteColumnWidth}px`, 'min-width': `${permissionsWriteColumnWidth}px` } });
const accessExecuteCell = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ 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();
const defaultReadCell = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.readHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component();
defaultPermissionsHeadersContainer.addItem(defaultReadCell, { CSSStyles: { 'width': `${permissionsReadColumnWidth}px`, 'min-width': `${permissionsReadColumnWidth}px` } });
const defaultWriteCell = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.writeHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component();
defaultPermissionsHeadersContainer.addItem(defaultWriteCell, { CSSStyles: { 'width': `${permissionsWriteColumnWidth}px`, 'min-width': `${permissionsWriteColumnWidth}px` } });
const defaultExecuteCell = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ 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;
}
// Access Permissions Group
const accessReadCell = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.readHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component();
headerRowContainer.addItem(accessReadCell, { CSSStyles: { 'width': `${permissionsReadColumnWidth}px`, 'min-width': `${permissionsReadColumnWidth}px` } });
const accessWriteCell = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.writeHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component();
headerRowContainer.addItem(accessWriteCell, { CSSStyles: { 'width': `${permissionsWriteColumnWidth}px`, 'min-width': `${permissionsWriteColumnWidth}px` } });
const accessExecuteCell = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ 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 defaultReadCell = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.readHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component();
headerRowContainer.addItem(defaultReadCell, { CSSStyles: { 'width': `${permissionsReadColumnWidth}px`, 'min-width': `${permissionsReadColumnWidth}px` } });
const defaultWriteCell = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.writeHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component();
headerRowContainer.addItem(defaultWriteCell, { CSSStyles: { 'width': `${permissionsWriteColumnWidth}px`, 'min-width': `${permissionsWriteColumnWidth}px` } });
const defaultExecuteCell = modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.executeHeader, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component();
headerRowContainer.addItem(defaultExecuteCell, { CSSStyles: { 'width': `${permissionsExecuteColumnWidth}px`, 'min-width': `${permissionsExecuteColumnWidth}px` } });
// Delete
const deleteCell = modelBuilder.text().component();
headerRowContainer.addItem(deleteCell, { CSSStyles: { 'width': `${permissionsDeleteColumnWidth}px`, 'min-width': `${permissionsDeleteColumnWidth}px` } });
rowsContainer.addItem(headerRowContainer);
return rowsContainer;
}
function createCheckbox(builder: azdata.ModelBuilder, checked: boolean, containerWidth: number, containerHeight: number): { container: azdata.FlexContainer, checkbox: azdata.CheckBoxComponent } {
@@ -388,7 +446,6 @@ function createCheckbox(builder: azdata.ModelBuilder, checked: boolean, containe
.withProperties<azdata.CheckBoxProperties>({ checked: checked, height: 20, width: 20 }).component();
const container = builder.flexContainer()
.withLayout({ width: containerWidth, height: containerHeight })
//.withItems([checkbox], { CSSStyles: { ...cssStyles.permissionCheckboxCss }})
.component();
container.addItem(checkbox, { CSSStyles: { ...cssStyles.permissionCheckboxCss } });
return {

View File

@@ -11,9 +11,10 @@ import * as through from 'through2';
import * as nls from 'vscode-nls';
import * as auth from '../util/auth';
import { IHdfsOptions, IRequestParams } from '../objectExplorerNodeProvider/fileSources';
import { IAclStatus, AclEntry, parseAcl, AclPermissionType, parseAclPermissionFromOctal, AclEntryScope } from './aclEntry';
import { PermissionStatus, AclEntry, parseAcl, PermissionType, parseAclPermissionFromOctal, AclEntryScope } from './aclEntry';
import { Mount } from './mount';
import { everyoneName } from '../localizedConstants';
import { FileStatus, parseHdfsFileType } from './fileStatus';
const localize = nls.loadMessageBundle();
const ErrorMessageInvalidDataStructure = localize('webhdfs.invalidDataStructure', "Invalid Data Structure");
@@ -75,7 +76,7 @@ export class WebHDFS {
endpoint.pathname = this._opts.path + path;
let searchOpts = Object.assign(
{ 'op': operation },
this._opts.user ? { 'user.name': this._opts.user } : {},
// this._opts.user ? { 'user.name': this._opts.user } : {},
params || {}
);
endpoint.search = querystring.stringify(searchOpts);
@@ -420,13 +421,42 @@ export class WebHDFS {
});
}
public getFileStatus(path: string, callback: (error: HdfsError, fileStatus: FileStatus) => void): void {
this.checkArgDefined('path', path);
let endpoint = this.getOperationEndpoint('getfilestatus', path);
this.sendRequest('GET', endpoint, undefined, (error, response) => {
if (!callback) { return; }
if (error) {
callback(error, undefined);
} else if (response.body.hasOwnProperty('FileStatus')) {
const fileStatus = new FileStatus(
response.body.FileStatus.accessTime || '',
response.body.FileStatus.blockSize || '',
response.body.FileStatus.group || '',
response.body.FileStatus.length || '',
response.body.FileStatus.modificationTime || '',
response.body.FileStatus.owner || '',
response.body.FileStatus.pathSuffix || '',
response.body.FileStatus.permission || '',
response.body.FileStatus.replication || '',
response.body.FileStatus.snapshotEnabled || '',
parseHdfsFileType(response.body.FileStatus.type || 'undefined')
);
callback(undefined, fileStatus);
} else {
callback(new HdfsError(ErrorMessageInvalidDataStructure), undefined);
}
});
}
/**
* Get ACL status for given path
* @param path The path to the file/folder to get the status of
* @param callback Callback to handle the response
* @returns void
*/
public getAclStatus(path: string, callback: (error: HdfsError, aclStatus: IAclStatus) => void): void {
public getAclStatus(path: string, callback: (error: HdfsError, permissionStatus: PermissionStatus) => void): void {
this.checkArgDefined('path', path);
let endpoint = this.getOperationEndpoint('getaclstatus', path);
@@ -436,14 +466,17 @@ export class WebHDFS {
callback(error, undefined);
} else if (response.body.hasOwnProperty('AclStatus')) {
const permissions = parseAclPermissionFromOctal(response.body.AclStatus.permission);
const aclStatus: IAclStatus = {
owner: new AclEntry(AclEntryScope.access, AclPermissionType.owner, '', response.body.AclStatus.owner || '', permissions.owner),
group: new AclEntry(AclEntryScope.access, AclPermissionType.group, '', response.body.AclStatus.group || '', permissions.group),
other: new AclEntry(AclEntryScope.access, AclPermissionType.other, '', everyoneName, permissions.other),
stickyBit: !!response.body.AclStatus.stickyBit,
entries: (<any[]>response.body.AclStatus.entries).map(entry => parseAcl(entry)).reduce((acc, parsedEntries) => acc.concat(parsedEntries), [])
};
callback(undefined, aclStatus);
const permissionStatus = new PermissionStatus(
new AclEntry(AclEntryScope.access, PermissionType.owner, '', response.body.AclStatus.owner || '', permissions.owner),
new AclEntry(AclEntryScope.access, PermissionType.group, '', response.body.AclStatus.group || '', permissions.group),
new AclEntry(AclEntryScope.access, PermissionType.other, '', everyoneName, permissions.other),
!!response.body.AclStatus.stickyBit,
// We filter out empty names here since those are already added by the permission bits - WebHDFS creates an extra ACL
// entry for the owning group whenever another ACL is added.
(<any[]>response.body.AclStatus.entries).map(entry => parseAcl(entry))
.reduce((acc, parsedEntries) => acc.concat(parsedEntries), [])
.filter(e => e.name !== ''));
callback(undefined, permissionStatus);
} else {
callback(new HdfsError(ErrorMessageInvalidDataStructure), undefined);
}
@@ -473,6 +506,20 @@ export class WebHDFS {
});
}
/**
* Sets the permission octal (sticky, owner, group & other) for a file/folder
* @param path The path to the file/folder to set the permission of
* @param permissionStatus The status containing the permission to set
*/
public setPermission(path: string, permissionStatus: PermissionStatus, callback: (error: HdfsError) => void): void {
this.checkArgDefined('path', path);
this.checkArgDefined('permissionStatus', permissionStatus);
let endpoint = this.getOperationEndpoint('setpermission', path, { permission: permissionStatus.permissionOctal });
this.sendRequest('PUT', endpoint, undefined, (error) => {
return callback && callback(error);
});
}
/**
* Get all mounts for a HDFS connection
* @param callback Callback to handle the response