mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-08 01:28:26 -05:00
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:
@@ -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).*$/",
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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)]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user