More HDFS Manage Access dialog updates (#7692)

* Add support for default permissions on directories

(cherry picked from commit 4e81cceba142c6763c3447b4d2965cd75764f8f9)

* Remove unneeded import

(cherry picked from commit ffe5f357357e75e9290966e89768c699df2e1311)

* Add recursive apply and clean up webhdfs

(cherry picked from commit ae76df14f99e599df1cdfcc74ee22d3822f11a59)

* Final set of changes

* Undo changes to azdata/sqlops and few minor fixes

* Remove cast to fix build error

* Hide defaults checkbox for files and switch checkbox order
This commit is contained in:
Charles Gagnon
2019-10-11 15:18:17 -07:00
committed by GitHub
parent 888327f5bc
commit d02c680dab
9 changed files with 466 additions and 162 deletions

View File

@@ -276,6 +276,11 @@
"when": "nodeType=~/^mssqlCluster/ && nodeType != mssqlCluster:connection && nodeType != mssqlCluster:message && nodeType != mssqlCluster:hdfs",
"group": "1mssqlCluster@3"
},
{
"command": "mssqlCluster.manageAccess",
"when": "nodeType=~/^mssqlCluster/ && nodeType != mssqlCluster:connection && nodeType != mssqlCluster:message",
"group": "1mssqlCluster@3"
},
{
"command": "mssqlCluster.deleteFiles",
"when": "nodeType=~/^mssqlCluster/ && nodeType != mssqlCluster:hdfs && nodeType != mssqlCluster:connection && viewItem != mssqlCluster:connection && nodeType != mssqlCluster:message && nodeSubType=~/^(?!:mount).*$/",

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IconPathHelper, IconPath } from '../iconHelper';
import { groupBy } from '../util/arrays';
/**
* The permission status of an HDFS path - this consists of :
@@ -32,7 +33,8 @@ export class PermissionStatus {
* @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}`;
// Always use the access scope for the permission octal - it doesn't have a concept of other scopes
return `${this.stickyBit ? '1' : ''}${this.owner.getPermissionDigit(AclEntryScope.access)}${this.group.getPermissionDigit(AclEntryScope.access)}${this.other.getPermissionDigit(AclEntryScope.access)}`;
}
}
@@ -135,23 +137,62 @@ function parseAclPermission(permissionString: string): AclEntryPermission {
* permission - The permission set for this ACL. @see AclPermission
*/
export class AclEntry {
private readonly permissions = new Map<AclEntryScope, AclEntryPermission>();
constructor(
public readonly scope: AclEntryScope,
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).
* Adds a new permission at the specified scope, overwriting the existing permission at that scope if it
* exists
* @param scope The scope to add the new permission at
* @param permission The permission to set
*/
public get permissionDigit(): number {
return this.permission.permissionDigit;
public addPermission(scope: AclEntryScope, permission: AclEntryPermission): void {
this.permissions.set(scope, permission);
}
/**
* Returns the string representation of the ACL Entry in the form [SCOPE:]TYPE:NAME:PERMISSION.
* Deletes the permission at the specified scope.
* @param scope The scope to delete the permission for
* @returns True if the entry was successfully deleted, false if not (it didn't exist)
*/
public removePermission(scope: AclEntryScope): boolean {
return this.permissions.delete(scope);
}
/**
* Gets the permission at the specified scope if one exists
* @param scope The scope to retrieve the permission for
*/
public getPermission(scope: AclEntryScope): AclEntryPermission | undefined {
return this.permissions.get(scope);
}
/**
* Gets the full list of permissions and their scopes for this entry
*/
public getAllPermissions(): { scope: AclEntryScope, permission: AclEntryPermission }[] {
return Array.from(this.permissions.entries()).map((entry: [AclEntryScope, AclEntryPermission]) => {
return { scope: entry[0], permission: entry[1] };
});
}
/**
* Gets the octal number representing the permission for the specified scope of
* this entry. This will either be a number between 0 and 7 inclusive (which is
* a bitwise OR the permission flags rwx) or undefined if the scope doesn't exist
* for this entry.
*/
public getPermissionDigit(scope: AclEntryScope): number | undefined {
return this.permissions.has(scope) ? this.permissions.get(scope).permissionDigit : undefined;
}
/**
* Returns the string representation of each 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
* specified.
* The name is optional and so may be empty.
@@ -161,8 +202,10 @@ export class AclEntry {
* user::r-x
* default:group::r--
*/
toAclString(): string {
return `${this.scope === AclEntryScope.default ? 'default:' : ''}${getAclEntryType(this.type)}:${this.name}:${this.permission.toString()}`;
toAclStrings(): string[] {
return Array.from(this.permissions.entries()).map((entry: [AclEntryScope, AclEntryPermission]) => {
return `${entry[0] === AclEntryScope.default ? 'default:' : ''}${getAclEntryType(this.type)}:${this.name}:${entry[1].toString()}`;
});
}
/**
@@ -174,9 +217,22 @@ export class AclEntry {
if (!other) {
return false;
}
return this.scope === other.scope &&
this.type === other.type &&
this.name === other.name;
return AclEntry.compare(this, other) === 0;
}
/**
* Compares two AclEntry objects for ordering
* @param a The first AclEntry to compare
* @param b The second AclEntry to compare
*/
static compare(a: AclEntry, b: AclEntry): number {
if (a.name === b.name) {
if (a.type === b.type) {
return 0;
}
return a.type.localeCompare(b.type);
}
return a.name.localeCompare(b.name);
}
}
@@ -218,11 +274,15 @@ function getAclEntryType(type: AclType | PermissionType): AclType {
* user:bob:rwx,user::rwx,default::bob:rwx,group::r-x,default:other:r--
* @param aclString The string representation of the ACL
*/
export function parseAcl(aclString: string): AclEntry[] {
export function parseAclList(aclString: string): AclEntry[] {
if (aclString === '') {
return [];
}
if (!/^(default:)?(user|group|mask|other):([A-Za-z_][A-Za-z0-9._-]*)?:([rwx-]{3})?(,(default:)?(user|group|mask|other):([A-Za-z_][A-Za-z0-9._-]*)?:([rwx-]{3})?)*$/.test(aclString)) {
throw new Error(`Invalid ACL string ${aclString}. Expected to match ^(default:)?(user|group|mask|other):[[A-Za-z_][A-Za-z0-9._-]]*:([rwx-]{3})?(,(default:)?(user|group|mask|other):[[A-Za-z_][A-Za-z0-9._-]]*:([rwx-]{3})?)*$`);
}
return aclString.split(',').map(aclEntryString => parseAclEntry(aclEntryString));
return mergeAclEntries(aclString.split(',').map(aclEntryString => parseAclEntry(aclEntryString)));
}
/**
@@ -253,7 +313,9 @@ export function parseAclEntry(aclString: string): AclEntry {
}
const name = parts[i++];
const permission = parseAclPermission(parts[i++]);
return new AclEntry(scope, type, name, name, permission);
const entry = new AclEntry(type, name, name);
entry.addPermission(scope, permission);
return entry;
}
/**
@@ -303,3 +365,19 @@ export function getImageForType(type: AclType | PermissionType): IconPath {
}
return { dark: '', light: '' };
}
/**
* Merges a list of AclEntry objects such that the resulting list contains only a single entry for each name/type pair with
* a separate permission for each separate AclEntry
* @param entries The set of AclEntries to merge
*/
function mergeAclEntries(entries: AclEntry[]): AclEntry[] {
const groupedEntries = groupBy(entries, (a, b) => AclEntry.compare(a, b)); // First group the entries together
return groupedEntries.map(entryGroup => { // Now make a single AclEntry for each group and add all the permissions from each group
const entry = new AclEntry(entryGroup[0].type, entryGroup[0].name, entryGroup[0].displayName);
entryGroup.forEach(e => {
e.getAllPermissions().forEach(sp => entry.addPermission(sp.scope, sp.permission));
});
return entry;
});
}

View File

@@ -3,10 +3,14 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { IFileSource } from '../objectExplorerNodeProvider/fileSources';
import { PermissionStatus, AclEntry, AclEntryScope, AclType, AclEntryPermission } from './aclEntry';
import { FileStatus } from './fileStatus';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
/**
* Model for storing the state of a specified file/folder in HDFS
@@ -29,7 +33,7 @@ export class HdfsModel {
*/
public fileStatus: FileStatus;
constructor(private fileSource: IFileSource, private path: string) {
constructor(private readonly fileSource: IFileSource, private readonly path: string) {
this.refresh();
}
@@ -53,7 +57,8 @@ export class HdfsModel {
if (!this.permissionStatus) {
return;
}
const newEntry = new AclEntry(AclEntryScope.access, type, name, name, new AclEntryPermission(true, true, true));
const newEntry = new AclEntry(type, name, name);
newEntry.addPermission(AclEntryScope.access, new AclEntryPermission(true, true, true));
// Don't add duplicates. This also checks the owner, group and other items
if ([this.permissionStatus.owner, this.permissionStatus.group, this.permissionStatus.other].concat(this.permissionStatus.aclEntries).find(entry => entry.isEqual(newEntry))) {
return;
@@ -78,10 +83,62 @@ 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<any> {
// TODO Apply recursive
public async apply(recursive: boolean = false): Promise<void> {
await this.applyAclChanges(this.path);
if (recursive) {
azdata.tasks.startBackgroundOperation(
{
connection: undefined,
displayName: localize('mssql.recursivePermissionOpStarted', "Applying permission changes recursively under '{0}'", this.path),
description: '',
isCancelable: false,
operation: async op => {
await this.applyToChildrenRecursive(op, this.path);
op.updateStatus(azdata.TaskStatus.Succeeded, localize('mssql.recursivePermissionOpSucceeded', "Permission changes applied successfully."));
}
}
);
}
}
/**
* Recursive call to apply the current set of changes to all children of this path (if any)
* @param op Background operation used to track status of the task
* @param path The path
*/
private async applyToChildrenRecursive(op: azdata.BackgroundOperation, path: string): Promise<void> {
try {
op.updateStatus(azdata.TaskStatus.InProgress, localize('mssql.recursivePermissionOpProgress', "Applying permission changes to '{0}'.", path));
const files = await this.fileSource.enumerateFiles(path, true);
// Apply changes to all children of this path and then recursively apply to children of any directories
await Promise.all(
[
files.map(file => this.applyAclChanges(file.path)),
files.filter(f => f.isDirectory).map(d => this.applyToChildrenRecursive(op, d.path))
]);
} catch (error) {
const errMsg = localize('mssql.recursivePermissionOpError', "Error applying permission changes: {0}", (error instanceof Error ? error.message : error));
vscode.window.showErrorMessage(errMsg);
op.updateStatus(azdata.TaskStatus.Failed, errMsg);
}
}
/**
* Applies the current set of Permissions/ACLs to the specified path
* @param path The path to apply the changes to
*/
private async applyAclChanges(path: string): Promise<any> {
// HDFS won't remove existing default ACLs even if you call setAcl with no default ACLs specified. You
// need to call removeDefaultAcl specifically to remove them.
if (!this.permissionStatus.owner.getPermission(AclEntryScope.default) &&
!this.permissionStatus.group.getPermission(AclEntryScope.default) &&
!this.permissionStatus.other.getPermission(AclEntryScope.default)) {
await this.fileSource.removeDefaultAcl(path);
}
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)]);
this.fileSource.setAcl(path, this.permissionStatus.owner, this.permissionStatus.group, this.permissionStatus.other, this.permissionStatus.aclEntries),
this.fileSource.setPermission(path, this.permissionStatus)]);
}
}

View File

@@ -6,7 +6,7 @@
import * as azdata from 'azdata';
import { HdfsModel } from '../hdfsModel';
import { IFileSource } from '../../objectExplorerNodeProvider/fileSources';
import { PermissionStatus, AclEntry, AclType, getImageForType } from '../../hdfs/aclEntry';
import { PermissionStatus, AclEntry, AclType, getImageForType, AclEntryScope, AclEntryPermission } from '../../hdfs/aclEntry';
import { cssStyles } from './uiConstants';
import * as loc from '../../localizedConstants';
import { HdfsError } from '../webhdfs';
@@ -16,7 +16,7 @@ import { HdfsFileType } from '../fileStatus';
const permissionsTypeIconColumnWidth = 35;
const permissionsNameColumnWidth = 250;
const permissionsStickyColumnWidth = 50;
const permissionsInheritColumnWidth = 50;
const permissionsReadColumnWidth = 50;
const permissionsWriteColumnWidth = 50;
const permissionsExecuteColumnWidth = 50;
@@ -25,6 +25,15 @@ const permissionsDeleteColumnWidth = 50;
const permissionsRowHeight = 35;
const locationLabelHeight = 23; // Fits the text size without too much white space
const checkboxSize = 20;
type PermissionCheckboxesMapping = {
model: AclEntry,
access: { read: azdata.CheckBoxComponent, write: azdata.CheckBoxComponent, execute: azdata.CheckBoxComponent },
default: { read: azdata.CheckBoxComponent, write: azdata.CheckBoxComponent, execute: azdata.CheckBoxComponent }
};
export class ManageAccessDialog {
private hdfsModel: HdfsModel;
@@ -32,14 +41,16 @@ export class ManageAccessDialog {
private modelBuilder: azdata.ModelBuilder;
private rootLoadingComponent: azdata.LoadingComponent;
private ownersPermissionsContainer: azdata.FlexContainer;
private othersPermissionsContainer: azdata.FlexContainer;
private stickyCheckbox: azdata.CheckBoxComponent;
private inheritDefaultsCheckbox: azdata.CheckBoxComponent;
private posixPermissionsContainer: azdata.FlexContainer;
private namedUsersAndGroupsPermissionsContainer: azdata.FlexContainer;
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;
constructor(private hdfsPath: string, private fileSource: IFileSource, private readonly apiWrapper: ApiWrapper) {
@@ -52,15 +63,16 @@ export class ManageAccessDialog {
this.dialog = this.apiWrapper.createDialog(loc.manageAccessTitle, 'HdfsManageAccess', true);
this.dialog.okButton.label = loc.applyText;
const applyRecursivelyButton = azdata.window.createButton(loc.applyRecursivelyText);
applyRecursivelyButton.onClick(async () => {
this.applyRecursivelyButton = azdata.window.createButton(loc.applyRecursivelyText);
this.applyRecursivelyButton.onClick(async () => {
try {
azdata.window.closeDialog(this.dialog);
await this.hdfsModel.apply(true);
} catch (err) {
this.apiWrapper.showErrorMessage(loc.errorApplyingAclChanges(err instanceof HdfsError ? err.message : err));
}
});
this.dialog.customButtons = [applyRecursivelyButton];
this.dialog.customButtons = [this.applyRecursivelyButton];
this.dialog.registerCloseValidator(async (): Promise<boolean> => {
try {
await this.hdfsModel.apply();
@@ -123,30 +135,16 @@ export class ManageAccessDialog {
.component();
contentContainer.addItem(permissionsTitle, { CSSStyles: { 'margin-top': '15px', ...cssStyles.titleCss } });
// ==============================
// = Owners permissions section =
// ==============================
// =============================
// = POSIX permissions section =
// =============================
const ownersPermissionsHeaderRow = this.createPermissionsHeaderRow(modelView.modelBuilder, loc.ownersHeader, true);
contentContainer.addItem(ownersPermissionsHeaderRow, { CSSStyles: { ...cssStyles.tableHeaderLayoutCss } });
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 group permissions
this.ownersPermissionsContainer = modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
contentContainer.addItem(this.ownersPermissionsContainer, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '20px' } });
// ==============================
// = Others permissions section =
// ==============================
const othersPermissionsHeaderRow = modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component();
const ownersHeaderCell = modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.allOthersHeader }).component();
othersPermissionsHeaderRow.addItem(ownersHeaderCell, { flex: '1 1 auto', CSSStyles: { 'width': `${permissionsNameColumnWidth}px`, 'min-width': `${permissionsNameColumnWidth}px`, ...cssStyles.tableHeaderCss } });
contentContainer.addItem(othersPermissionsHeaderRow, { CSSStyles: { ...cssStyles.tableHeaderLayoutCss } });
// Empty initially - this is eventually going to be populated with the "Everyone" permissions
this.othersPermissionsContainer = modelView.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
contentContainer.addItem(this.othersPermissionsContainer, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '20px' } });
// 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 =
@@ -165,7 +163,7 @@ export class ManageAccessDialog {
this.addUserOrGroupSelectedType = AclType.user;
typeContainer.addItems([userTypeButton, groupTypeButton], { flex: '0 0 auto' });
contentContainer.addItem(typeContainer, { 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()
@@ -203,7 +201,7 @@ export class ManageAccessDialog {
// = Named Users and Groups permissions header row =
// =================================================
const namedUsersAndGroupsPermissionsHeaderRow = this.createPermissionsHeaderRow(modelView.modelBuilder, loc.namedUsersAndGroupsHeader, false);
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
@@ -219,6 +217,7 @@ export class ManageAccessDialog {
this.dialog.content = [tab];
}
this.applyRecursivelyButton.hidden = true; // Always hide the button until we get the status back saying whether this is a directory or not
azdata.window.openDialog(this.dialog);
}
@@ -227,24 +226,26 @@ export class ManageAccessDialog {
return;
}
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');
// Owners
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, permissionStatus.other, false);
this.othersPermissionsContainer.clearItems();
this.othersPermissionsContainer.addItem(otherPermissionsRow, { CSSStyles: { 'border-bottom': cssStyles.tableBorderCss, 'border-top': cssStyles.tableBorderCss, 'margin-right': '14px' } });
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' } });
this.namedUsersAndGroupsPermissionsContainer.clearItems();
// Named users and groups
permissionStatus.aclEntries.forEach(entry => {
const namedEntryRow = this.createPermissionsRow(this.modelBuilder, entry, true);
const namedEntryRow = this.createPermissionsRow(this.modelBuilder, entry, /*includeDelete*/true, /*includeInherit*/true);
this.namedUsersAndGroupsPermissionsContainer.addItem(namedEntryRow, { CSSStyles: { 'border-bottom': cssStyles.tableBorderCss, 'border-top': cssStyles.tableBorderCss } });
});
@@ -259,18 +260,7 @@ export class ManageAccessDialog {
return button;
}
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, 2, { flex: '0 0 auto' });
return row;
}
private createPermissionsRow(builder: azdata.ModelBuilder, entry: AclEntry, includeDelete: boolean): azdata.FlexContainer {
private createPermissionsRow(builder: azdata.ModelBuilder, entry: AclEntry, includeDelete: boolean, includeInherit: boolean): azdata.FlexContainer {
const rowContainer = builder.flexContainer().withLayout({ flexFlow: 'row', height: permissionsRowHeight }).component();
// Icon
@@ -290,50 +280,92 @@ export class ManageAccessDialog {
rowContainer.addItem(nameCell);
// Access - Read
const accessReadComponents = createCheckbox(builder, entry.permission.read, permissionsReadColumnWidth, permissionsRowHeight);
const accessReadComponents = createCheckbox(builder, entry.getPermission(AclEntryScope.access).read, true, permissionsReadColumnWidth, permissionsRowHeight);
rowContainer.addItem(accessReadComponents.container, { flex: '0 0 auto' });
accessReadComponents.checkbox.onChanged(() => {
entry.permission.read = accessReadComponents.checkbox.checked;
entry.getPermission(AclEntryScope.access).read = accessReadComponents.checkbox.checked;
});
// Access - Write
const accessWriteComponents = createCheckbox(builder, entry.permission.write, permissionsWriteColumnWidth, permissionsRowHeight);
const accessWriteComponents = createCheckbox(builder, entry.getPermission(AclEntryScope.access).write, true, permissionsWriteColumnWidth, permissionsRowHeight);
rowContainer.addItem(accessWriteComponents.container, { flex: '0 0 auto' });
accessWriteComponents.checkbox.onChanged(() => {
entry.permission.write = accessWriteComponents.checkbox.checked;
entry.getPermission(AclEntryScope.access).write = accessWriteComponents.checkbox.checked;
});
// Access - Execute
const accessExecuteComponents = createCheckbox(builder, entry.permission.execute, permissionsExecuteColumnWidth, permissionsRowHeight);
const accessExecuteComponents = createCheckbox(builder, entry.getPermission(AclEntryScope.access).execute, true, permissionsExecuteColumnWidth, permissionsRowHeight);
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;
entry.getPermission(AclEntryScope.access).execute = accessExecuteComponents.checkbox.checked;
});
const permissionsCheckboxesMapping: PermissionCheckboxesMapping = {
model: entry,
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
if (this.hdfsModel.fileStatus.type === HdfsFileType.Directory) {
const defaultPermission = entry.getPermission(AclEntryScope.default);
const defaultReadComponents = createCheckbox(builder, defaultPermission && defaultPermission.read, !!defaultPermission, permissionsReadColumnWidth, permissionsRowHeight);
const defaultWriteComponents = createCheckbox(builder, defaultPermission && defaultPermission.write, !!defaultPermission, permissionsWriteColumnWidth, permissionsRowHeight);
const defaultExecuteComponents = createCheckbox(builder, defaultPermission && defaultPermission.execute, !!defaultPermission, permissionsExecuteColumnWidth, permissionsRowHeight);
permissionsCheckboxesMapping.default = { read: defaultReadComponents.checkbox, write: defaultWriteComponents.checkbox, execute: defaultExecuteComponents.checkbox };
// Default - Inherit
if (includeInherit) {
const defaultInheritComponents = createCheckbox(builder, !defaultPermission, !this.inheritDefaultsCheckbox.checked, permissionsInheritColumnWidth, permissionsRowHeight);
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;
} 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,
new AclEntryPermission(accessRead, accessWrite, accessExecute));
}
});
this.namedSectionInheritCheckboxes.push(defaultInheritComponents.checkbox);
rowContainer.addItem(defaultInheritComponents.container, { flex: '0 0 auto', CSSStyles: { 'border-right': cssStyles.tableBorderCss } });
}
// 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
entry.getPermission(AclEntryScope.default).read = defaultReadComponents.checkbox.checked;
});
// 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
defaultWriteComponents.checkbox.onChanged(() => {
entry.getPermission(AclEntryScope.default).write = defaultWriteComponents.checkbox.checked;
});
// 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
defaultExecuteComponents.checkbox.onChanged(() => {
entry.getPermission(AclEntryScope.default).execute = defaultExecuteComponents.checkbox.checked;
});
}
this.posixPermissionCheckboxesMapping.push(permissionsCheckboxesMapping);
const deleteContainer = builder.flexContainer().withLayout({ width: permissionsDeleteColumnWidth, height: permissionsRowHeight }).component();
@@ -351,23 +383,74 @@ export class ManageAccessDialog {
deleteButton.onDidClick(() => { this.hdfsModel.deleteAclEntry(entry); });
deleteContainer.addItem(deleteButton);
}
rowContainer.addItem(deleteContainer, { flex: '0 0 auto', CSSStyles: { 'margin-top': '5px', 'margin-left': '5px' } });
rowContainer.addItem(deleteContainer, { flex: '0 0 auto', CSSStyles: { 'margin-top': '7px', 'margin-left': '5px' } });
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.
* 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 includeSticky Whether to include the sticky header
*/
private createPermissionsHeaderRow(modelBuilder: azdata.ModelBuilder, nameColumnText: string, includeSticky: boolean): azdata.FlexContainer {
private createPermissionsHeaderRow(modelBuilder: azdata.ModelBuilder, nameColumnText: string, includeInherit: boolean, includeStickyAndInherit: boolean): azdata.FlexContainer {
const rowsContainer = modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
// Section Headers
const sectionHeaderContainer = modelBuilder.flexContainer().withLayout({ flexFlow: 'row', justifyContent: 'flex-end' }).component();
if (includeStickyAndInherit) {
this.inheritDefaultsCheckbox = modelBuilder.checkBox()
.withProperties<azdata.CheckBoxProperties>({
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);
}
// Access
const accessSectionHeader = modelBuilder.text()
.withProperties<azdata.TextComponentProperties>({
value: loc.accessHeader,
@@ -378,8 +461,9 @@ export class ManageAccessDialog {
}
})
.component();
sectionHeaderContainer.addItem(accessSectionHeader, { flex: '0 0 auto' });
// Default
const defaultSectionHeader = modelBuilder.text()
.withProperties<azdata.TextComponentProperties>({
value: loc.defaultHeader,
@@ -392,6 +476,7 @@ export class ManageAccessDialog {
.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` } });
@@ -401,17 +486,25 @@ export class ManageAccessDialog {
// 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' });
if (includeStickyAndInherit) {
// Sticky
this.stickyCheckbox = modelBuilder.checkBox()
.withProperties<azdata.CheckBoxProperties>({
width: checkboxSize,
height: checkboxSize,
checked: false, // Will be set when we get the model update
label: loc.stickyLabel
})
.component();
this.stickyCheckbox.onChanged(() => {
this.hdfsModel.permissionStatus.stickyBit = this.stickyCheckbox.checked;
});
headerRowContainer.addItem(this.stickyCheckbox);
}
// 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();
@@ -422,6 +515,10 @@ export class ManageAccessDialog {
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<azdata.TextComponentProperties>({ value: loc.inheritDefaultsLabel, CSSStyles: { ...cssStyles.permissionsTableHeaderCss } }).component();
defaultPermissionsHeadersContainer.addItem(inheritCell, { CSSStyles: { 'width': `${permissionsInheritColumnWidth}px`, 'min-width': `${permissionsInheritColumnWidth}px` } });
}
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();
@@ -441,9 +538,9 @@ export class ManageAccessDialog {
}
}
function createCheckbox(builder: azdata.ModelBuilder, checked: boolean, containerWidth: number, containerHeight: number): { container: azdata.FlexContainer, checkbox: azdata.CheckBoxComponent } {
function createCheckbox(builder: azdata.ModelBuilder, checked: boolean, enabled: boolean, containerWidth: number, containerHeight: number): { container: azdata.FlexContainer, checkbox: azdata.CheckBoxComponent } {
const checkbox = builder.checkBox()
.withProperties<azdata.CheckBoxProperties>({ checked: checked, height: 20, width: 20 }).component();
.withProperties({ checked: checked, enabled: enabled, height: checkboxSize, width: checkboxSize }).component();
const container = builder.flexContainer()
.withLayout({ width: containerWidth, height: containerHeight })
.component();

View File

@@ -11,9 +11,9 @@ import * as through from 'through2';
import * as nls from 'vscode-nls';
import * as auth from '../util/auth';
import { IHdfsOptions, IRequestParams } from '../objectExplorerNodeProvider/fileSources';
import { PermissionStatus, AclEntry, parseAcl, PermissionType, parseAclPermissionFromOctal, AclEntryScope } from './aclEntry';
import { PermissionStatus, AclEntry, parseAclList, PermissionType, parseAclPermissionFromOctal, AclEntryScope, AclType } from './aclEntry';
import { Mount } from './mount';
import { everyoneName } from '../localizedConstants';
import { everyoneName, ownerPostfix, owningGroupPostfix } from '../localizedConstants';
import { FileStatus, parseHdfsFileType } from './fileStatus';
const localize = nls.loadMessageBundle();
@@ -340,11 +340,11 @@ export class WebHDFS {
}
/**
* Read directory contents
* List the status of a path
*
* @returns void
*/
public readdir(path: string, callback: (error: HdfsError, files: any[]) => void): void {
public listStatus(path: string, callback: (error: HdfsError, files: FileStatus[]) => void): void {
this.checkArgDefined('path', path);
let endpoint = this.getOperationEndpoint('liststatus', path);
@@ -356,7 +356,21 @@ export class WebHDFS {
callback(error, undefined);
} else if (response.body.hasOwnProperty('FileStatuses')
&& response.body.FileStatuses.hasOwnProperty('FileStatus')) {
files = response.body.FileStatuses.FileStatus;
files = (<any[]>response.body.FileStatuses.FileStatus).map(fs => {
return new FileStatus(
fs.accessTime || '',
fs.blockSize || '',
fs.group || '',
fs.length || '',
fs.modificationTime || '',
fs.owner || '',
fs.pathSuffix || '',
fs.permission || '',
fs.replication || '',
fs.snapshotEnabled || '',
parseHdfsFileType(fs.type)
);
});
callback(undefined, files);
} else {
callback(new HdfsError(ErrorMessageInvalidDataStructure), undefined);
@@ -401,26 +415,6 @@ export class WebHDFS {
});
}
/**
* Get file status for given path
* @returns void
*/
public stat(path: string, callback: (error: HdfsError, fileStatus: any) => 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')) {
callback(undefined, response.body.FileStatus);
} else {
callback(new HdfsError(ErrorMessageInvalidDataStructure), undefined);
}
});
}
public getFileStatus(path: string, callback: (error: HdfsError, fileStatus: FileStatus) => void): void {
this.checkArgDefined('path', path);
@@ -466,16 +460,45 @@ export class WebHDFS {
callback(error, undefined);
} else if (response.body.hasOwnProperty('AclStatus')) {
const permissions = parseAclPermissionFromOctal(response.body.AclStatus.permission);
const ownerEntry = new AclEntry(PermissionType.owner, '', `${response.body.AclStatus.owner || ''}${ownerPostfix}`);
ownerEntry.addPermission(AclEntryScope.access, permissions.owner);
const groupEntry = new AclEntry(PermissionType.group, '', `${response.body.AclStatus.group || ''}${owningGroupPostfix}`);
groupEntry.addPermission(AclEntryScope.access, permissions.group);
const otherEntry = new AclEntry(PermissionType.other, '', everyoneName);
otherEntry.addPermission(AclEntryScope.access, permissions.other);
const parsedEntries = parseAclList((<any[]>response.body.AclStatus.entries).join(','));
// First go through and apply any ACLs for the unnamed entries (which correspond to the permissions in
// the permission octal)
parsedEntries.filter(e => e.name === '').forEach(e => {
let targetEntry: AclEntry;
switch (e.type) {
case AclType.user:
targetEntry = ownerEntry;
break;
case AclType.group:
targetEntry = groupEntry;
break;
case AclType.other:
targetEntry = otherEntry;
break;
default:
// Unknown type - just ignore since we don't currently support the other types
return;
}
e.getAllPermissions().forEach( sp => {
targetEntry.addPermission(sp.scope, sp.permission);
});
});
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),
ownerEntry,
groupEntry,
otherEntry,
!!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 !== ''));
// We filter out empty names here since those have already been merged into the
// owner/owning group/other entries
parsedEntries.filter(e => e.name !== ''));
callback(undefined, permissionStatus);
} else {
callback(new HdfsError(ErrorMessageInvalidDataStructure), undefined);
@@ -491,7 +514,6 @@ export class WebHDFS {
* @param otherEntry The entry corresponding to default permissions for all other users
* @param aclEntries The optional additional ACL entries to set
* @param callback Callback to handle the response
* @returns void
*/
public setAcl(path: string, ownerEntry: AclEntry, groupEntry: AclEntry, otherEntry: AclEntry, aclEntries: AclEntry[], callback: (error: HdfsError) => void): void {
this.checkArgDefined('path', path);
@@ -499,7 +521,8 @@ export class WebHDFS {
this.checkArgDefined('groupEntry', groupEntry);
this.checkArgDefined('otherEntry', otherEntry);
this.checkArgDefined('aclEntries', aclEntries);
const aclSpec = [ownerEntry, groupEntry, otherEntry].concat(aclEntries).map(entry => entry.toAclString()).join(',');
const concatEntries = [ownerEntry, groupEntry, otherEntry].concat(aclEntries);
const aclSpec = concatEntries.reduce((acc, entry) => acc.concat(entry.toAclStrings()), []).join(',');
let endpoint = this.getOperationEndpoint('setacl', path, { aclspec: aclSpec });
this.sendRequest('PUT', endpoint, undefined, (error) => {
return callback && callback(error);
@@ -510,6 +533,7 @@ 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
* @param callback Callback to handle the response
*/
public setPermission(path: string, permissionStatus: PermissionStatus, callback: (error: HdfsError) => void): void {
this.checkArgDefined('path', path);
@@ -520,6 +544,19 @@ export class WebHDFS {
});
}
/**
* Removes the default ACLs for the specified path
* @param path The path to remove the default ACLs for
* @param callback Callback to handle the response
*/
public removeDefaultAcl(path: string, callback: (error: HdfsError) => void): void {
this.checkArgDefined('path', path);
let endpoint = this.getOperationEndpoint('removedefaultacl', path);
this.sendRequest('PUT', endpoint, undefined, (error) => {
return callback && callback(error);
});
}
/**
* Get all mounts for a HDFS connection
* @param callback Callback to handle the response
@@ -550,7 +587,7 @@ export class WebHDFS {
public exists(path: string, callback: (error: HdfsError, exists: boolean) => void): void {
this.checkArgDefined('path', path);
this.stat(path, (error, fileStatus) => {
this.listStatus(path, (error, fileStatus) => {
let exists = !fileStatus ? false : true;
callback(error, exists);
});

View File

@@ -14,15 +14,16 @@ export const msgMissingNodeContext = localize('msgMissingNodeContext', 'Node Com
export const manageAccessTitle = localize('mssql.manageAccessTitle', "Manage Access");
export const locationTitle = localize('mssql.locationTitle', "Location : ");
export const permissionsHeader = localize('mssql.permissionsTitle', "Permissions");
export const ownersHeader = localize('mssql.ownersHeader', "Owners");
export const allOthersHeader = localize('mssql.allOthersHeader', "All Others");
export const everyoneName = localize('mssql.everyone', "Everyone");
export const ownerPostfix = localize('mssql.ownerPostfix', " - Owner");
export const owningGroupPostfix = localize('mssql.owningGroupPostfix', " - Owning Group");
export const everyoneName = localize('mssql.everyone', "Everyone else");
export const userLabel = localize('mssql.userLabel', "User");
export const groupLabel = localize('mssql.groupLabel', "Group");
export const accessHeader = localize('mssql.accessHeader', "Access");
export const defaultHeader = localize('mssql.defaultHeader', "Default");
export const deleteTitle = localize('mssql.delete', "Delete");
export const stickyHeader = localize('mssql.stickyHeader', "Sticky");
export const stickyLabel = localize('mssql.stickyHeader', "Sticky");
export const inheritDefaultsLabel = localize('mssql.inheritDefaultsLabel', "Inherit Defaults");
export const readHeader = localize('mssql.readHeader', "Read");
export const writeHeader = localize('mssql.writeHeader', "Write");
export const executeHeader = localize('mssql.executeHeader', "Execute");

View File

@@ -17,7 +17,7 @@ import * as constants from '../constants';
import { WebHDFS, HdfsError } from '../hdfs/webhdfs';
import { AclEntry, PermissionStatus } from '../hdfs/aclEntry';
import { Mount, MountStatus } from '../hdfs/mount';
import { FileStatus } from '../hdfs/fileStatus';
import { FileStatus, HdfsFileType } from '../hdfs/fileStatus';
const localize = nls.loadMessageBundle();
@@ -87,6 +87,11 @@ export interface IFileSource {
* @param aclEntries The ACL entries to set
*/
setAcl(path: string, ownerEntry: AclEntry, groupEntry: AclEntry, otherEntry: AclEntry, aclEntries: AclEntry[]): Promise<void>;
/**
* Removes the default ACLs for the specified path
* @param path The path to remove the default ACLs for
*/
removeDefaultAcl(path: string): Promise<void>;
/**
* 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
@@ -120,11 +125,6 @@ export interface IRequestParams {
headers?: {};
}
export interface IHdfsFileStatus {
type: 'FILE' | 'DIRECTORY';
pathSuffix: string;
}
export class FileSourceFactory {
private static _instance: FileSourceFactory;
@@ -181,7 +181,7 @@ export class HdfsFileSource implements IFileSource {
if (!this.mounts || refresh) {
await this.loadMounts();
}
return this.readdir(path);
return this.listStatus(path);
}
private loadMounts(): Promise<void> {
@@ -196,16 +196,15 @@ export class HdfsFileSource implements IFileSource {
});
}
private readdir(path: string): Promise<IFile[]> {
private listStatus(path: string): Promise<IFile[]> {
return new Promise((resolve, reject) => {
this.client.readdir(path, (error, files) => {
this.client.listStatus(path, (error, fileStatuses) => {
if (error) {
reject(error);
}
else {
let hdfsFiles: IFile[] = files.map(fileStat => {
let hdfsFile = <IHdfsFileStatus>fileStat;
let file = new File(File.createPath(path, hdfsFile.pathSuffix), hdfsFile.type === 'DIRECTORY');
let hdfsFiles: IFile[] = fileStatuses.map(fileStatus => {
let file = new File(File.createPath(path, fileStatus.pathSuffix), fileStatus.type === HdfsFileType.Directory);
if (this.mounts && this.mounts.has(file.path)) {
file.mountStatus = MountStatus.Mount;
}
@@ -403,6 +402,22 @@ export class HdfsFileSource implements IFileSource {
});
}
/**
* Removes the default ACLs for the specified path
* @param path The path to remove the default ACLs for
*/
public removeDefaultAcl(path: string): Promise<void> {
return new Promise((resolve, reject) => {
this.client.removeDefaultAcl(path, (error: HdfsError) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
/**
* 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

View File

@@ -19,4 +19,18 @@ export function equals<T>(one: ReadonlyArray<T>, other: ReadonlyArray<T>, itemEq
export function flatten<T>(arr: ReadonlyArray<T>[]): T[] {
return ([] as T[]).concat.apply([], arr);
}
}
export function groupBy<T>(data: ReadonlyArray<T>, compare: (a: T, b: T) => number): T[][] {
const result: T[][] = [];
let currentGroup: T[] | undefined = undefined;
for (const element of data.slice(0).sort(compare)) {
if (!currentGroup || compare(currentGroup[0], element) !== 0) {
currentGroup = [element];
result.push(currentGroup);
} else {
currentGroup.push(element);
}
}
return result;
}

View File

@@ -20,7 +20,7 @@ import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/work
@Component({
selector: 'modelview-checkbox',
template: `
<div #input width="100%"></div>
<div #input width="100%" [style.display]="display"></div>
`
})
export default class CheckBoxComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {