Added theme support

This commit is contained in:
Hugo Bernier 2020-04-06 21:50:19 -04:00
parent e4e1a044b1
commit a23babceac
36 changed files with 18935 additions and 8008 deletions

View File

@ -1,8 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"version": "1.1.1",
"isDomainIsolated": false,
"isCreatingSolution": false,
"packageManager": "npm",
"version": "1.10.0",
"libraryName": "spsecurity-webpart-3",
"libraryId": "788271fb-ee9b-40df-8381-eb3dc70d1982",
"environment": "spo"
"environment": "spo",
"componentType": "webpart"
}
}

View File

@ -13,25 +13,27 @@ extensions:
- react
createdDate: 12/1/2017 12:00:00 AM
---
# SPFx React Grid
# SPFX React Grid
## Summary
React-securitygrid is an SPFx webpart that uses React and Office-UI-Fabric to render a grid showing which users have access to which lists/libraries/folders/files on a Web as shown here:
React-securitygrid is an SPFX webpart that uses React and Office-UI-Fabric to render a grid showing which users have access to which lists/libraries/folders/files on a Web as shown here:
![config panel](./src/images/MainDisplay.PNG)
![config panel](./src/images/MainDisplay.gif)
Empty libraries are displayed with a black folder icon, those with items are displayed with a white folder. The user can expand a list or library by clicking on the desired row. For deeply nested folders the Title column can be resized by drag and drop. The display shows a 'filled-in' circle if the user has the selected permission to the given list, library, file or folder. (NOTE:The grid does not currently take into account access give via membership in an active directory group). The user must have permissions to access lists and enumerate permissions in order to view the grid.
Empty libraries are displayed with a black folder icon, those with items are displayed with a white folder. The user can expand a list or library by clicking on the desired row. (If the library or folder has more than 5000 items an error will be displayed ) For deeply nested folders the Title column can be resized by drag and drop. The display shows the appropriate icon circle if the user has the selected permission to the given list, library, file or folder. (NOTE:The grid does not currently take into account access give via membership in an active directory group-- coming soon!).
The user can change the permission being tested by clicking the Permission in the command bar and selecting a new Permission:
> IMPORTANT: The user must have permissions to access lists and enumerate permissions in order to view the grid.
The user can change the permission being tested by clicking the Permission in the command bar and selecting the new Permission:
![permission panel](./src/images/selectPermissionsPopout.PNG)
The user can change which users are being shown in the grid by selecting the users button in the command bar and selecting a desired users:
The user can change which users are being shown in the grid by selecting the users button in the command bar and selecting the desired users:
![Select users](./src/images/SelectUsersPopout.PNG)
The user can change which lists are being shown in the grid by selecting the lists button in the command bar and selecting a desired lists:
The user can change which lists are being shown in the grid by selecting the lists button in the command bar and selecting the desired lists:
![Select Lists](./src/images/Selectlistspopout.PNG)
@ -39,36 +41,45 @@ The user can change alternate between displaying user names and emails selectin
![Select Mode](./src/images/SelectDisplayModePopout.PNG)
The the first configuration panel of the webpart is shown below:
The first configuration panel of the webpart is shown below:
![config panel](./src/images/Configuration.PNG)
Permission Settings
### Permission Settings
The Permission Type dropdown sets the default permission to check.The 'Let user select Permission' checkbox determines whether the user can change this permission.
The Permission Settings allow you to select which permissions to show in the grid and to select the Icon and color used to display the selected permission.
User Settings
### User Settings
The Show Email or Name Toggle determines whether the name or email is displayed by default.
The Show Security Groups checkbox determines whether SharePoint Security groups are included in the grid.
The Show Users checkbox determines whether Users are included in the grid.
The Only show users with permissions toggle determines whether the grid should display all users with access to the web, or only users with the selected permission
![config panel](./src/images/Permissions.gif)
The Let Users Select users checkbox determines whether Users can filter the selected users in the grid.
Display Settings
### Display Settings
The Initial Title column width determines the initial width of the Title column(it can be resized).
The second configuration panel allows the owner to configure the List Settings
![List Configuration panel](./src/images/ListConfiguration.PNG)
List Settings
### List Settings
The Show Hidden Lists checkbox determines whether Hidden lists are displayed.
The Show System Lists checkbox determines whether System Lists (Catalogs) are included in the grid.
The Show Users checkbox determines whether Users are included in the grid.
The Let Users Select lists checkbox determines whether Users can filter the selected lists in the grid.
Select Lists
### Select Lists
The Include/Exclude Selected lists Toggle determines whether the lists selected are to be included or excluded.
@ -80,7 +91,7 @@ This is a port of an Angular 1.3 SharePoint hosted App at https://github.com/rus
## Used SharePoint Framework Version
![version](https://img.shields.io/badge/version-1.4.1-green.svg)
![version](https://img.shields.io/badge/version-1.10-green.svg)
## Applies to
@ -101,6 +112,7 @@ Solution|Author(s)
Version|Date|Comments
-------|----|--------
1.0.0.2|April 5, 2021| Updates to SPFx 1.10; Allow display of multiple permissions
1.0.0.1|April 25, 2018|Update to SPFx 1.4.1
1.0.0.0|December 31, 2016|Initial version

View File

@ -1,5 +1,5 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"sp-security-bundle": {

View File

@ -1,4 +1,4 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx-build/copy-assets.schema.json",
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "temp/deploy"
}

View File

@ -1,5 +1,5 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "spsecurity-webpart-3",

View File

@ -1,6 +1,8 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"includeClientSideAssets": true,
"isDomainIsolated": false,
"name": "spsecurity-webpart-3-client-side-solution",
"id": "788271fb-ee9b-40df-8381-eb3dc70d1982",
"version": "1.0.0.1"

View File

@ -1,10 +1,15 @@
{
"$schema": "https://dev.office.com/json-schemas/core-build/serve.schema.json",
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"initialPage": "https://localhost:5432/workbench",
"https": true,
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
},
"serveConfigurations": {
"default": {
"pageUrl": "https://russellwgove.sharepoint.com/sites/workspaces/_layouts/15/workbench.aspx"
}
}
}

View File

@ -1,45 +0,0 @@
{
"$schema": "https://dev.office.com/json-schemas/core-build/tslint.schema.json",
// Display errors as warnings
"displayAsWarning": true,
// The TSLint task may have been configured with several custom lint rules
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
// project). If true, this flag will deactivate any of these rules.
"removeExistingRules": true,
// When true, the TSLint task is configured with some default TSLint "rules.":
"useDefaultConfigAsBase": false,
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
// which are active, other than the list of rules below.
"lintConfig": {
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-case": true,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"valid-typeof": true,
"variable-name": false,
"whitespace": false
}
}
}

View File

@ -1,4 +1,4 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx-build/write-manifests.schema.json",
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -7,4 +7,5 @@ build.addSuppression(`Warning - [sass] The local CSS class 'ms-DetailsHeader' is
build.addSuppression(`Warning - [sass] The local CSS class 'ms-DetailsHeader-cell' is not camelCase and will not be type-safe.`);
build.addSuppression(`Warning - [sass] The local CSS class 'ms-List-cell' is not camelCase and will not be type-safe.`);
build.addSuppression(`Warning - [sass] The local CSS class 'ms-DetailsRow' is not camelCase and will not be type-safe.`);
build.initialize(gulp);

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +1,49 @@
{
"main": "lib/index.js",
"name": "spsecurity-webpart-3",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"resolutions": {
"@types/react": "16.8.8"
},
"dependencies": {
"@microsoft/sp-build-web": "~1.4.1",
"@microsoft/sp-core-library": "~1.4.1",
"@microsoft/sp-module-interfaces": "~1.4.1",
"@microsoft/sp-webpart-base": "~1.4.1",
"@microsoft/sp-webpart-workbench": "~1.4.1",
"@pnp/common": "1.3.3",
"@pnp/logging": "1.3.3",
"@pnp/odata": "1.3.3",
"@pnp/sp": "1.3.3",
"@pnp/spfx-property-controls": "1.0.0",
"@types/react": "15.0.38",
"@types/react-addons-shallow-compare": "0.14.17",
"@types/react-addons-test-utils": "0.14.15",
"@types/react-addons-update": "0.14.14",
"@types/react-dom": "0.14.18",
"@types/webpack-env": ">=1.12.1 <1.14.0",
"lodash": "^4.17.15",
"office-ui-fabric-react": "^4.21.2",
"react": "15.4.2",
"react-dom": "15.4.2"
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"lodash": "^4.17.4",
"natives": "^1.1.6",
"office-ui-fabric-react": "6.189.2",
"react": "16.8.5",
"react-dom": "16.8.5"
},
"devDependencies": {
"@microsoft/sp-build-web": "~1.4.1",
"@microsoft/sp-module-interfaces": "~1.4.1",
"@microsoft/sp-webpart-workbench": "~1.4.1",
"@types/chai": ">=3.4.34 <3.6.0",
"@types/mocha": ">=2.2.33 <2.6.0",
"gulp": "~3.9.1"
"@microsoft/rush-stack-compiler-3.2": "0.6.8",
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
"@microsoft/sp-build-web": "1.10.0",
"@microsoft/sp-core-library": "1.10.0",
"@microsoft/sp-lodash-subset": "1.10.0",
"@microsoft/sp-module-interfaces": "1.10.0",
"@microsoft/sp-office-ui-fabric-core": "1.10.0",
"@microsoft/sp-property-pane": "1.10.0",
"@microsoft/sp-tslint-rules": "1.10.0",
"@microsoft/sp-webpart-base": "1.10.0",
"@microsoft/sp-webpart-workbench": "1.10.0",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2",
"gulp": "~3.9.1",
"i": "0.3.6",
"npm": "6.14.4",
"tslint-microsoft-contrib": "5.0.0"
},
"scripts": {
"build": "gulp bundle",

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -1,49 +0,0 @@
{
// Display errors as warnings
"displayAsWarning": true,
// The TSLint task may have been configured with several custom lint rules
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
// project). If true, this flag will deactivate any of these rules.
"removeExistingRules": true,
// When true, the TSLint task is configured with some default TSLint "rules.":
"useDefaultConfigAsBase": false,
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
// which are active, other than the list of rules below.
"lintConfig": {
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-case": true,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": false,
"no-unused-imports": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"valid-typeof": true,
"variable-name": false,
"whitespace": false,
"no-debugger":false,
"no-unused-variable": false,
"max-line-lenth": false
}
}
}

View File

@ -1,7 +1,7 @@
import {sp} from "@pnp/sp";
import { sp } from "@pnp/sp";
import { find, indexOf, includes } from "lodash";
import { SPPermission } from "@microsoft/sp-page-context";
import { GraphHttpClient, HttpClientResponse, IGraphHttpClientOptions } from "@microsoft/sp-http";
import { AadHttpClient, HttpClientResponse, IAadHttpClientOptions } from "@microsoft/sp-http";
export interface ISPSecurableObject {
id: number;
@ -69,6 +69,7 @@ export class SPSecurityInfo {
this.siteUsers = new Array<SPSiteUser>();
this.lists = new Array<SPList>();
}
}
@ -110,7 +111,20 @@ export class SPRoleAssignment {
}
export class Helpers {
public static doesUserHaveAnyPermission(securableObjects: any[], user, requestedpermissions: SPPermission[], roles, siteGroups): boolean {
for (var securableObject of securableObjects) {
for (var requestedpermission of requestedpermissions) {
if (Helpers.doesUserHavePermission(securableObject, user, requestedpermission, roles, siteGroups)) {
return true;
}
}
}
return false;
}
public static doesUserHavePermission(securableObject, user, requestedpermission: SPPermission, roles, siteGroups) {
console.log(`check user ${user.name} obj ${securableObject.title} perm ${requestedpermission.value.High}${requestedpermission.value.Low} `);
const permissions: SPBasePermissions[] = Helpers.getUserPermissionsForObject(securableObject, user, roles, siteGroups);
for (const permission of permissions) {
if (
@ -193,7 +207,7 @@ export class Helpers {
}
return selectedRoleAssignments;
} catch (exception) {
debugger;
//debugger;
console.error(exception);
}
@ -291,26 +305,27 @@ export default class SPSecurityService {
return itemsToAdd;
});
}
public async getMembersOfAdGroup(graphHttpClient: GraphHttpClient, groupName: string): Promise<any> {
// public async getMembersOfAdGroup(aadHttpClient: AadHttpClient, groupName: string): Promise<any> {
return graphHttpClient.get("v1.0/groups?$filter=displayName eq '" + groupName + "'&$expand=members",
GraphHttpClient.configurations.v1).then((response) => {
response.json().then((data) => {
debugger;
});
}).catch((err) => {
});
}
// return aadHttpClient.get("v1.0/groups?$filter=displayName eq '" + groupName + "'&$expand=members",
// AadHttpClient.configurations.v1).then((response) => {
// response.json().then((data) => {
// debugger;
// });
// }).catch((err) => {
// console.log(err);
// });
// }
/// Loads data for intial display
public loadData(showHiddenLists: boolean, showCatalogs: boolean, graphHttpClient: GraphHttpClient, forceReload: boolean): Promise<SPSecurityInfo> {
public loadData(showHiddenLists: boolean, showCatalogs: boolean, aadHttpClient: AadHttpClient, forceReload: boolean): Promise<SPSecurityInfo> {
let securityInfo: SPSecurityInfo = new SPSecurityInfo();
let batch: any = sp.createBatch();
let errors: Array<string> = [];
sp.web.siteUsers
.inBatch(batch).get().then((response) => {
sp.web.siteUsers.inBatch(batch).get()
.then((response) => {
console.table(response);
securityInfo.siteUsers = response.map((u) => {
let user: SPSiteUser = new SPSiteUser();
user.isSelected = true;
user.id = u.Id;
@ -325,9 +340,14 @@ export default class SPSecurityService {
return user;
});
return securityInfo.siteUsers;
}).catch((error) => {
debugger;
errors.push(`There was an error feting site users -- ${error.message}`);
throw error;
});
sp.web.siteGroups.expand("Users").select("Title", "Id", "IsHiddenInUI", "IsShareByEmailGuestUse", "IsSiteAdmin", "IsSiteAdmin")
.inBatch(batch).get().then((response) => {
sp.web.siteGroups.filter(`Title ne 'Limited Access System Group'`).expand("Users").select("Title", "Id", "IsHiddenInUI", "IsShareByEmailGuestUse", "IsSiteAdmin", "IsSiteAdmin")
.inBatch(batch).get()
.then((response) => {
let AdGroupPromises: Array<Promise<any>> = [];
// if group contains an ad group(PrincipalType=4) expand it
securityInfo.siteGroups = response.map((grp) => {
@ -365,8 +385,14 @@ export default class SPSecurityService {
return securityInfo.siteGroups;
});
}).catch((error) => {
//error fetching groups
errors.push(`There was an error feting site Groups -- ${error.message}`);
//debugger;
throw error;
});
sp.web.roleDefinitions.expand("BasePermissions").inBatch(batch).get().then((response) => {
sp.web.roleDefinitions.expand("BasePermissions").inBatch(batch).get()
.then((response) => {
securityInfo.roleDefinitions = response.map((rd) => {
const bp: SPBasePermissions = new SPBasePermissions(rd.BasePermissions.High, rd.BasePermissions.Low);
@ -379,8 +405,12 @@ export default class SPSecurityService {
return roleDefinition;
});
return securityInfo.roleDefinitions;
}).catch((error) => {
//debugger;
//error fetching roledefinitions
errors.push(`There was an error fetcing role Definitions -- ${error.message}`);
throw error;
});
let filters: string[] = [];
if (!showHiddenLists) {
@ -393,9 +423,8 @@ export default class SPSecurityService {
sp.web.lists
.expand("RootFolder", "RoleAssignments", "RoleAssignments/RoleDefinitionBindings", "RoleAssignments/Member",
"RoleAssignments/Member/Users", "RoleAssignments/Member/Groups", "RoleAssignments/Member/UserId")
.filter(filter)
.inBatch(batch).get().then((response) => {
.filter(filter).inBatch(batch).get()
.then((response) => {
securityInfo.lists = response.map((listObject) => {
let mylist: SPList = new SPList();
mylist.isSelected = true;// Shoudl be shown in the UI, user can deslect it in the ui
@ -418,9 +447,20 @@ export default class SPSecurityService {
return mylist;
});
}).catch((error) => {
//debugger;
errors.push(`There was an error fetching lists -- ${error.message} `);
//error fetching lists
throw error;
});
return batch.execute().then(() => {
return securityInfo;
}).catch((error) => {
//debugger;
// error in batch
throw errors;
});
}
}

View File

@ -1,20 +1,29 @@
import { SPSiteUser } from "../SPSecurityService";
import { SPPermission } from "@microsoft/sp-page-context";
export interface ISelectedPermission {
permission: string;
color: string;
iconName:string;
freindlyName:string;
isChecked?:boolean;
}
export interface ISpSecurityWebPartProps {
users: SPSiteUser[];
permission: string;
//permission: string; // used if only one permission selected.... gonzo, all new now
selectedPermissions: ISelectedPermission[];// used if multiple permissions selected
showHiddenLists: boolean;
showCatalogs:boolean;
letUserSelectPermission:boolean;
letUserSelectUsers:boolean;
letUserSelectLists:boolean;
includeAdminSelectedLists:boolean; // true to inlude them, false to excluder
adminSelectedLists:string[];
listTitleColumnWidth:number;
showEmail:boolean; //0 show name, 1 show email
showSecurityGroups:boolean; // show PrincipalType=4
showUsers:boolean; // show PrincipalType=1
showCatalogs: boolean;
letUserSelectPermission: boolean;
letUserSelectUsers: boolean;
letUserSelectLists: boolean;
includeAdminSelectedLists: boolean; // true to inlude them, false to excluder
adminSelectedLists: string[];
listTitleColumnWidth: number;
showEmail: boolean; //0 show name, 1 show email
showSecurityGroups: boolean; // show PrincipalType=4
showUsers: boolean; // show PrincipalType=1
showOnlyUsersWithPermission;// toggle to show everyone, or justy the users who have the permissions
}

View File

@ -1,5 +1,5 @@
{
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "41e37f03-2ea8-4f19-b77a-f2121a1e7c45",
"alias": "SpSecurityWebPart",
"componentType": "WebPart",
@ -8,13 +8,17 @@
/**
* This property should only be set to true if it is certain that the webpart does not
* allow arbitrary scripts to be called
*
"requiresCustomScript": true,
*/
"safeWithCustomScriptDisabled": false,
"supportedHosts": [
"SharePointWebPart"
],
"preconfiguredEntries": [
{
"groupId": "41e37f03-2ea8-4f19-b77a-f2121a1e7c45",
"group": {
"default": "Under Development"
"default": "Other"
},
"title": {
"default": "SPSecurity"
@ -33,7 +37,29 @@
"includeAdminSelectedLists": false,
"listTitleColumnWidth": 120,
"showEmail": false,
"showUsers": true
"showUsers": true,
"showOnlyUsersWithPermission": true,
"selectedPermissions": [
{
"permission": "manageLists",
"color": "#0000FF",
"iconName": "Settings",
"freindlyName": "Manage"
},
{
"permission": "editListItems",
"color": "#FF0000",
"iconName": "Edit",
"freindlyName": "Edit"
},
{
"permission": "viewListItems",
"color": "#00FF00",
"iconName": "View",
"freindlyName": "View"
}
]
}
}
]

View File

@ -3,22 +3,19 @@ import * as ReactDom from "react-dom";
import { Version } from "@microsoft/sp-core-library";
import { SPPermission } from "@microsoft/sp-page-context";
import { PropertyFieldListPicker, PropertyFieldListPickerOrderBy } from '@pnp/spfx-property-controls/lib/PropertyFieldListPicker';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField,
PropertyPaneDropdown, IPropertyPaneDropdownOption,
PropertyPaneCheckbox,
PropertyPaneToggle
} from "@microsoft/sp-webpart-base";
import {sp} from "@pnp/sp";
import { PropertyFieldSelectedPermissions, IPropertyFieldSelectedPermissionsProps } from "./containers/PropertyFieldSelectedPermissions";
import { sp } from "@pnp/sp";
import * as strings from "spSecurityStrings";
import SpSecurity from "./components/SpSecurity";
import { ISpSecurityProps } from "./components/ISpSecurityProps";
import { ISpSecurityWebPartProps } from "./ISpSecurityWebPartProps";
import { PropertyPaneSlider } from "@microsoft/sp-webpart-base/lib/propertyPane/propertyPaneFields/propertyPaneSlider/PropertyPaneSlider";
import PropertyPane from "@microsoft/sp-webpart-base/lib/propertyPane/propertyPane/PropertyPane";
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
import {
IPropertyPaneConfiguration, PropertyPaneCheckbox,
IPropertyPaneDropdownOption, PropertyPaneDropdown, PropertyPaneTextField,
PropertyPaneToggle, PropertyPaneSlider
} from "@microsoft/sp-property-pane";
export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurityWebPartProps> {
public onInit(): Promise<void> {
@ -31,15 +28,18 @@ export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurity
});
});
}
public render(): void {
const props: ISpSecurityProps = {
permission: this.properties.permission,
//permission: this.properties.permission, // old way
selectedPermissions: this.properties.selectedPermissions.map((spp) => { return { ...spp, isChecked: true }; }),
showHiddenLists: this.properties.showHiddenLists,
showCatalogs: this.properties.showCatalogs,
showEmail: this.properties.showEmail,
showSecurityGroups: this.properties.showSecurityGroups,
showUsers: this.properties.showUsers,
showOnlyUsersWithPermission: this.properties.showOnlyUsersWithPermission,
letUserSelectPermission: this.properties.letUserSelectPermission,
letUserSelectUsers: this.properties.letUserSelectUsers,
letUserSelectLists: this.properties.letUserSelectLists,
@ -48,8 +48,8 @@ export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurity
listTitleColumnWidth: this.properties.listTitleColumnWidth,
users: this.properties.users,
getPermissionTypes: this.getPermissionTypes,
graphHttpClient: this.context.graphHttpClient,
domElement : this.domElement
aadHttpClient: null,//this.context.aadHttpClient,
domElement: this.domElement
};
const element: React.ReactElement<ISpSecurityProps> = React.createElement(
@ -75,21 +75,41 @@ export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurity
}
return perms;
}
private onPropertyChange(propertyPath: string, oldValue: any, newValue: any) {
//debugger;
// does this get oldvalue and new value?
switch (propertyPath) {
case "SelectedPermissions":
this.properties.selectedPermissions = newValue;
this.context.propertyPane.refresh();
this.render();
break;
default:
break;
}
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: "Configuration"
description: "Permission Configuration"
},
groups: [
{
groupName: "Permission Settings",
groupFields: [
PropertyPaneDropdown("permission", {
label: "Permission Type",
options: this.getPermissionTypes()
PropertyFieldSelectedPermissions("SelectedPermissions", {
label: "Selected Permissions and Colors",
onPropertyChange: this.onPropertyChange.bind(this),
getSelectedPermissions: () => {
return this.properties.selectedPermissions || [];
},
}),
PropertyPaneCheckbox("letUserSelectPermission", {
text: "Let user select Permission"
@ -112,7 +132,11 @@ export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurity
PropertyPaneCheckbox("showUsers", {
text: "Show Users"
}),
PropertyPaneToggle("showOnlyUsersWithPermission", {
label: "Only show users with permission",
onText: "Show users only if they have permission",
offText: "Show all users",
}),
PropertyPaneCheckbox("letUserSelectUsers", {
text: "Let user select Users"
})
@ -130,6 +154,42 @@ export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurity
}
]
},
{
header: {
description: "Permission Configuration"
},
groups: [
{
groupName: "Permission Settings",
groupFields: [
PropertyFieldSelectedPermissions("SelectedPermissions", {
label: "Selected Permissions and Colors",
onPropertyChange: this.onPropertyChange.bind(this),
getSelectedPermissions: () => {
return this.properties.selectedPermissions || [];
},
}),
PropertyPaneCheckbox("letUserSelectPermission", {
text: "Let user select Permission"
}),
]
},
{
groupName: "Display Settings",
groupFields: [
PropertyPaneSlider("listTitleColumnWidth", {
label: "Initial title column width",
min: 1,
max: 1000
}),
]
}
]
},
{
header: {
description: "Configure Lists"

View File

@ -1,14 +1,17 @@
import { SPSiteUser } from "../../SPSecurityService";
import { SPPermission } from "@microsoft/sp-page-context";
import {IPropertyPaneDropdownOption} from "@microsoft/sp-webpart-base";
import { GraphHttpClient } from "@microsoft/sp-http";
import { } from "@microsoft/sp-webpart-base";
import { IPropertyPaneDropdownOption, PropertyPaneDropdown } from "@microsoft/sp-property-pane";
import { AadHttpClient } from "@microsoft/sp-http";
import {ISelectedPermission} from "../ISpSecurityWebPartProps";
export interface ISpSecurityProps {
users: SPSiteUser[];
permission: string;
//permission: string;
selectedPermissions:ISelectedPermission[];
showHiddenLists: boolean;
showCatalogs:boolean;
getPermissionTypes:()=> IPropertyPaneDropdownOption[];
graphHttpClient: GraphHttpClient;
aadHttpClient: AadHttpClient;
letUserSelectPermission:boolean;
letUserSelectUsers:boolean;
letUserSelectLists:boolean;
@ -18,6 +21,7 @@ export interface ISpSecurityProps {
showEmail:boolean; //0 show name, 1 show email
showSecurityGroups:boolean; // show PrincipalType=4
showUsers:boolean; // show PrincipalType=1
showOnlyUsersWithPermission:boolean; //// toggle to show everyone, or justy the users who have the permissions
domElement:any; // needed to disable button postback after render on classic pages

View File

@ -1,10 +1,14 @@
import { SPSecurityInfo } from "../../SPSecurityService";
import { ISelectedPermission } from "../ISpSecurityWebPartProps";
export interface ISpSecurityState {
securityInfo: SPSecurityInfo;
permission: string;
showUserPanel:boolean;
showListPanel:boolean;
showEmail:boolean; //0 show name, 1 show email
securityInfoLoaded:boolean;
// permission: string;
selectedPermissions: ISelectedPermission[];
showUserPanel: boolean;
showListPanel: boolean;
showPermissionsPanel: boolean;
showEmail: boolean; //0 show name, 1 show email
securityInfoLoaded: boolean;
errors: Array<string>;
}

View File

@ -0,0 +1,39 @@
import * as React from 'react';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { Stack, StackItem, Alignment } from 'office-ui-fabric-react/lib/Stack';
import { IconButton, DefaultButton, PrimaryButton } from 'office-ui-fabric-react/lib/Button';
import { ISelectedPermission } from "../ISpSecurityWebPartProps";
export interface ILegendProps {
selectedPermissions: Array<ISelectedPermission>;
checkUncheckPermission: (perm: ISelectedPermission) => void;
}
export function Legend(props: ILegendProps): JSX.Element {
//debugger;
return (<Stack horizontal verticalFill wrap={true} gap={3}>
{
props.selectedPermissions.map((sp) =>
<div>
<DefaultButton
text={sp.freindlyName}
onClick={(e) => {
props.checkUncheckPermission(sp);
}}
checked={sp.isChecked}
title={sp.freindlyName}
iconProps={{ iconName: sp.iconName, style: { color: sp.color } }}>
{sp.freindlyName}
</DefaultButton>
&nbsp;
</div>
)
}
</Stack>
);
// return (<div>
// {
// props.selectedPermissions.map((sp)=><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{sp.freindlyName}&nbsp;:&nbsp;</span><span><Icon iconName={sp.iconName} style={{color:sp.color?sp.color:"FFFFFF"}} /></span></span> )
// }
// </div>
// );
}

View File

@ -3,8 +3,9 @@
.spSecurity {
}
.themecolor {
color: $ms-color-themePrimary;
color: $ms-color-themeDarkAlt;
}
.nonbrandeddocumentcolor {
color: "#00FF00";
}
@ -36,7 +37,15 @@
width: 40px !important;
}
.itemTitle {
cursor: pointer;
}
:global(.ms-List-cell):nth-child(even) :global(.ms-DetailsRow) {
background: $ms-color-neutralLight;
background: '[theme:neutralLight, default: #eaeaea]';
&:hover {
background: '[theme:neutralLighter, default: #f4f4f4]';
}
}
}

View File

@ -2,7 +2,7 @@ import * as React from "react";
import styles from "./SpSecurity.module.scss";
import { ISpSecurityProps } from "./ISpSecurityProps";
import { ISpSecurityState } from "./ISpSecurityState";
import { ILegendProps, Legend } from "./Legend";
import SPSecurityService from "../../SPSecurityService";
import { SPListItem, SPList, SPSiteUser, Helpers } from "../../SPSecurityService";
import { SPPermission } from "@microsoft/sp-page-context";
@ -11,17 +11,18 @@ import { DetailsList, IColumn, SelectionMode, IDetailsRowProps, Selection } from
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox';
import { CommandBar } from "office-ui-fabric-react/lib/CommandBar";
import { Stack } from "office-ui-fabric-react/lib/Stack";
import { Spinner } from "office-ui-fabric-react/lib/Spinner";
import { IContextualMenuItem, ContextualMenuItemType } from "office-ui-fabric-react/lib/ContextualMenu";
import { ISelectedPermission } from "../ISpSecurityWebPartProps";
import { Panel, PanelType } from "office-ui-fabric-react/lib/Panel";
import { right } from "glamor";
import SelectedPermissionsPanel from "../containers/SelectedPermissionsPanel";
import { css } from "@uifabric/utilities/lib/css";
import {
Environment,
EnvironmentType
} from '@microsoft/sp-core-library';
/* tslint:disable */
export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSecurityState> {
private svc: SPSecurityService = new SPSecurityService("ss");
private userSelection = new Selection();
@ -31,11 +32,14 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
super(props);
this.state = {
securityInfo: { siteUsers: [], siteGroups: [], roleDefinitions: [], lists: [] },
permission: this.props.permission,
//permission: this.props.permission,
selectedPermissions: this.props.selectedPermissions,
showUserPanel: false,
showListPanel: false,
showEmail: this.props.showEmail,
securityInfoLoaded: false
securityInfoLoaded: false,
showPermissionsPanel: false,
errors:[]
};
@ -64,19 +68,34 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
}
}
}
public componentWillMount(): void {
public componentWillReceiveProps(newProps: ISpSecurityProps) {
this.svc.loadData(this.props.showHiddenLists, this.props.showCatalogs, this.props.graphHttpClient, false).then((response) => {
this.setState((current) => ({
...current,
selectedPermissions: newProps.selectedPermissions,
showEmail: newProps.showEmail
}));
}
public componentWillMount(): void {
//debugger;
this.svc.loadData(this.props.showHiddenLists, this.props.showCatalogs, this.props.aadHttpClient, false)
.then((response) => {
const state: ISpSecurityState = {
securityInfo: response,
permission: this.props.permission,
// permission: this.props.permission,
selectedPermissions: this.props.selectedPermissions ? this.props.selectedPermissions : [],
showUserPanel: false,
showListPanel: false,
showPermissionsPanel: false,
showEmail: this.props.showEmail,
securityInfoLoaded: true
securityInfoLoaded: true,
errors:[]
};
// inlclude\exclude lists selected in property pane
//debugger;
state.securityInfo.lists = state.securityInfo.lists.filter((list) => {
if (this.props.includeAdminSelectedLists) { // include the lists
@ -96,8 +115,9 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
}
});
this.setState(state);
}).catch((err) => {
debugger;
}).catch((errors:Array<string>) => {
this.setState((current)=>({...current,errors:errors,securityInfoLoaded:true}))
//debugger;
});
}
public expandList(item: any): any {
@ -135,8 +155,10 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
item.isFetched = true;
this.setState(this.state);
}).catch((err) => {
debugger;
}).catch((error) => {
let errors=this.state.errors;
errors.push(`There was an error fetching site users -- ${error.message}`);
this.setState((current)=>({...current,errors:errors}))
});
}
@ -180,30 +202,27 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
}
public renderItemTitle(item?: any, index?: number, column?: IColumn): any {
let extension = item.title.split('.').pop();
let classname = "ms-u-smOffset" + (item.level);
if (this.validBrandIcons.indexOf(" " + extension + " ") !== -1) {
classname += " ms-Icon ms-BrandIcon--" + extension + " ms-BrandIcon--icon16 ";
}
else {
classname += " ms-Icon ms-Icon--TextDocument " + styles.themecolor;
}
const extension = item.title.split('.').pop();
const isValidExtension: boolean = (this.validBrandIcons.indexOf(" " + extension + " ") !== -1);
const classname = css("ms-u-smOffset" + (item.level), isValidExtension ?
`ms-Icon ms-BrandIcon--${extension} ms-BrandIcon--icon16`:
`ms-Icon ms-Icon--TextDocument ${styles.themecolor}`);
return (
<div>
<div className={styles.itemTitle} >
<div className={classname} />
<span >&nbsp;{item.title}</span>
<span>&nbsp;{item.title}</span>
</div>);
}
public renderListTitle(item?: any, index?: number, column?: IColumn): any {
let classname = " ms-Icon ";
if (item.itemCount > 0) {
classname += " ms-Icon ms-Icon--FabricFormLibrary " + styles.themecolor;
} else {
classname += " ms-Icon ms-Icon--FabricFolder ";
}
const classname = css("ms-Icon", styles.themecolor, item.itemCount > 0 ?
'ms-Icon ms-Icon--FabricFormLibrary':
'ms-Icon ms-Icon--FabricFolder');
return (
<div onClick={(e) => {
<div className={styles.itemTitle} onClick={(e) => {
//debugger;
this.expandCollapseList(item);
}}>
<div className={classname} />
@ -213,15 +232,12 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
}
public renderFolderTitle(item?: any, index?: number, column?: IColumn): any {
let classname = "ms-u-smOffset" + (item.level);
if (item.itemCount > 0) {
classname += " ms-Icon ms-Icon--FabricFormLibrary " + styles.themecolor;
} else {
classname += " ms-Icon ms-Icon--FabricFolder ";
}
const classname = css("ms-u-smOffset" + (item.level),styles.themecolor, item.itemCount > 0 ?
'ms-Icon ms-Icon--FabricFormLibrary':
'ms-Icon ms-Icon--FabricFolder');
return (
<div onClick={(e) => {
<div className={styles.itemTitle} onClick={(e) => {
this.expandCollapseList(item);
}}>
<div className={classname} />
@ -241,56 +257,32 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
}
}
// public getIcon(item?: any, index?: number, column?: IColumn): string {
// debugger;
// let classname: string = "";
// if (item instanceof SPList || item.type==="Folder") {
// if (item.itemCount === 0) {
// return "FabricFolderFill";
// } else {
// return "FabricFolder";
// }
// } else{
// return "ms-Icon ms-Icon--ExcelDocument"
// }
// }
// public renderTitle(item?: any, index?: number, column?: IColumn): any {
// let classname: string = "";
// if (item instanceof SPListItem) {
// classname = "ms-u-smOffset" + (item.level);
// }
// return (
// <div className={classname}>
// <div style={{ float: "left" }}>
// <Icon iconName={this.getIcon(item, index, column)} onClick={(e) => {
// this.expandCollapseList(item);
// }} />
// </div>
// <div>&nbsp;{item.title}</div>
// <div style={{ clear: "both" }} />
// </div>
// );
// }
public renderUserItem(item?: any, index?: number, column?: IColumn): any {
public renderUserItem(item: any, index: number, column: IColumn,effectivePermissions:ISelectedPermission[]): any {
let user: SPSiteUser = find(this.state.securityInfo.siteUsers, (su) => {
return su.id.toString() === column.key;
});
if (Helpers.doesUserHavePermission(item, user, SPPermission[this.state.permission],
// spin througg the selected permsiisopns and for the first hit, display that color. No Hit, then display empty
for (let selectedPermission of effectivePermissions ? effectivePermissions : []) {
if (Helpers.doesUserHavePermission(item, user, SPPermission[selectedPermission.permission],
this.state.securityInfo.roleDefinitions, this.state.securityInfo.siteGroups)) {
return (
<Icon iconName="CircleFill" onClick={(e) => {
this.expandCollapseList(item);
}} />
);
} else {
return (
<Icon iconName="LocationCircle" onClick={(e) => {
<Icon iconName={selectedPermission.iconName} style={{ color: selectedPermission.color }} onClick={(e) => {
this.expandCollapseList(item);
}} />
);
}
}
// no hits
return (
<Icon iconName={item.iconName} onClick={(e) => {
this.expandCollapseList(item);
}} />
);
}
public renderUserSelected(item?: SPSiteUser, index?: number, column?: IColumn): any {
return (
@ -298,7 +290,7 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
)
}
public addUserColumns(columns: IColumn[], users: SPSiteUser[]): IColumn[] {
public addUserColumns(columns: IColumn[], users: SPSiteUser[],effectivePermissions:ISelectedPermission[]): IColumn[] {
for (let user of users) {
if (user.isSelected) {
if (
@ -306,14 +298,19 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
||
(user.principalType === 4 && this.props.showSecurityGroups)
)
if (!this.props.showOnlyUsersWithPermission || Helpers.doesUserHaveAnyPermission(this.state.securityInfo.lists, user, effectivePermissions.map((sp) => { return SPPermission[sp.permission] }), this.state.securityInfo.roleDefinitions, this.state.securityInfo.siteGroups))
columns.push({
key: user.id.toString(),
name: this.state.showEmail ? user.upn : user.name,
fieldName: "",
minWidth: 20,
maxWidth: 20,
onRender: this.renderUserItem,
onRender: (item?: any, index?: number, column?: IColumn)=>{
//debugger;
return this.renderUserItem(item,index,column,effectivePermissions);
},
headerClassName: styles.rotatedColumnHeader,
});
}
}
@ -362,7 +359,17 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
}
private checkUncheckPermission(perm: ISelectedPermission) {
//debugger;
var sps = this.state.selectedPermissions;
const idx = findIndex(sps, (sp: ISelectedPermission) => { return sp.permission == perm.permission; });
if (idx != -1) {
sps[idx].isChecked = !sps[idx].isChecked
this.setState((current) => ({ ...current, SelectedPermissions: [...sps] }));
}
}
public render(): React.ReactElement<ISpSecurityProps> {
if (!this.state.securityInfoLoaded) {
return (
@ -372,7 +379,6 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
);
}
debugger;
let userPanelCommands: IContextualMenuItem[] = [];
userPanelCommands.push({
icon: "BoxAdditionSolid",
@ -380,11 +386,9 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
name: "Add All Users",
itemType: ContextualMenuItemType.Normal,
onClick: (event, item) => {
for (let item of this.state.securityInfo.siteUsers) {
item.isSelected = true;
}
this.setState(this.state);
}
});
@ -434,22 +438,17 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
});
let commands: IContextualMenuItem[] = [];
if (this.props.letUserSelectPermission) {
commands.push({
title: "Permission",
name: "Permission:",
key:
"permissionlabel"
})
commands.push({
icon: "AzureKeyVault",
key: "SecurityLevel",
title: "Permission",
label: "sss",
name: this.state.permission ? this.state.permission : "Select Permission",
key: "SecurityLevel2",
name: "Permission",
itemType: ContextualMenuItemType.Normal,
items: this.getPermissionLevels()
});
onClick: (event, item) => {
this.setState((current) => ({ ...current, showPermissionsPanel: !current.showPermissionsPanel }));
}
}
);
}
if (this.props.letUserSelectUsers) {
commands.push({
@ -480,26 +479,24 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
title: "DisplayMode",
name: this.state.showEmail ? "Show Email" : "Show Name",
itemType: ContextualMenuItemType.Normal,
subMenuProps: {
items: [{
key: "ShowName",
name: "Show Name",
onClick: (event, item) => {
debugger;
this.setState((current) => ({ ...current, showEmail: false }));
}
},
{
key: "ShowEmail",
name: "Show Email",
onClick: (event, item) => {
debugger;
this.setState((current) => ({ ...current, showEmail: true }));
}
}]
}
});
let effectivePermissions=this.state.selectedPermissions.filter((sp)=>{ return sp.isChecked;})
let columns: Array<IColumn> = [
{
key: "title", name: "Title", isResizable: true, fieldName: "title",
@ -507,7 +504,7 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
onRender: this.renderTitle, isRowHeader: true
},
];
let displayColumns: IColumn[] = this.addUserColumns(columns, this.state.securityInfo.siteUsers);
let displayColumns: IColumn[] = this.addUserColumns(columns, this.state.securityInfo.siteUsers,effectivePermissions);
let displayItems: (SPList | SPListItem)[] = filter(this.state.securityInfo.lists, (item) => {
@ -518,17 +515,49 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
)
})
let errorMessages=[];
for (let error of this.state.errors){
errorMessages.push(<li>{error}</li>)
}
return (
<div >
<ul>{errorMessages}</ul>
<CommandBar
items={commands}
/>
<br />
<Legend
selectedPermissions={this.state.selectedPermissions}
checkUncheckPermission={(e) => {
//debugger;
this.checkUncheckPermission(e);
}
}
/>
<DetailsList
items={displayItems}
columns={displayColumns}
selectionMode={SelectionMode.none}
className={styles.SPFXSecurityGrid}
/>
<SelectedPermissionsPanel
isOpen={this.state.showPermissionsPanel}
SelectedPermissions={this.props.selectedPermissions}
onPropertyChange={(propertyName: string, oldValue: Array<ISelectedPermission>, newValue: Array<ISelectedPermission>) => {
this.setState((current) => ({ ...current, selectedPermissions: newValue }));
}}
closePanel={() => {
this.setState((current) => ({ ...current, showPermissionsPanel: false }));
}}
>
</SelectedPermissionsPanel>
<Panel
isBlocking={false}
isOpen={this.state.showUserPanel}
@ -561,6 +590,7 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
headerText='Select Lists'
closeButtonAriaLabel='Close'>
<CommandBar items={listPanelCommands} />
<DetailsList
selection={this.listSelection}

View File

@ -0,0 +1,208 @@
import * as React from 'react';
import { Button, PrimaryButton } from 'office-ui-fabric-react/lib/Button';
import { Dropdown, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
import { ColorPicker } from "office-ui-fabric-react/lib/ColorPicker";
import { IColor } from "office-ui-fabric-react/lib/Color";
import { Dialog } from "office-ui-fabric-react/lib/Dialog";
import { TextField } from "office-ui-fabric-react/lib/TextField";
import { Icon } from "office-ui-fabric-react/lib/Icon";
import { ComboBox, IComboBox, IComboBoxOption, } from "office-ui-fabric-react/lib/ComboBox";
import { Label } from 'office-ui-fabric-react/lib/Label';
import { ISelectedPermission } from "../ISpSecurityWebPartProps";
import { SPPermission } from "@microsoft/sp-page-context";
import { findIndex } from "lodash";
export interface IColorIconSelectorPanelProps {
isOpen: boolean;
onPermissionChange(color: ISelectedPermission): void;
closePanel(): void;
title: string;
subText: string;
currentPerm: ISelectedPermission;
SelectedPermissions: ISelectedPermission[];
}
export interface IColorIconSelectionPanelState {
currentPerm: ISelectedPermission;
}
export default class ColorIconSelectionPanel extends React.Component<IColorIconSelectorPanelProps, IColorIconSelectionPanelState> {
private iconNames = [
"Add",
"Back",
"BlockContact",
"Cancel",
"Cat",
"CheckBox",
"CheckBoxComposite",
"CheckMark",
"ChevronDown",
"ChevronUp",
"CircleFill",
"CircleRing",
"Clear",
"Contact",
"Contrast",
"Delete",
"Design",
"eDiscovery",
"Edit",
"EditSolid12",
"Emoji2",
"Error",
"FavoriteStar",
"FavoriteStarFill",
"Glasses",
"HomeSolid",
"IncidentTriangle",
"LaptopSecure",
"LifeSaver",
"LifeSaverLock",
"Lock",
"LockSolid",
"Mail",
"More",
"PageLink",
"People",
"PeopleAdd",
"Permissions",
"PinSolid12",
"RedEye",
"Sad",
"SecurityGroup",
"Settings",
"Share",
"Sync",
"TriangleSolid",
"UnEditable",
"UnEditable2",
"Unlock",
"UserWarning",
"View",
];
constructor(props: IColorIconSelectorPanelProps) {
super(props);
//debugger;
this.state = {
currentPerm: this.props.currentPerm
};
}
private saveChanges(): void {
this.props.onPermissionChange(this.state.currentPerm);
this.onClosePanel();
}
private onClosePanel(element?: any): void {
this.props.closePanel();
}
private renderIcon(props?, defaultRender?: (props?) => JSX.Element | null): JSX.Element {
return (<div>
<Icon iconName={props.text} /> {props.text}
</div>);
}
private getIconOptions(): IComboBoxOption[] {
let options: IComboBoxOption[] = [];
for (var iconName of this.iconNames) {
var option: IComboBoxOption = {
key: iconName, text: iconName,
};
options.push(option);
}
return options;
}
public getPermissionTypes(): IDropdownOption[] {
let perms = new Array<IDropdownOption>();
for (const perm in SPPermission) {
if (typeof (SPPermission[perm]) === "object") {
perms.push({
text: perm,
key: perm,
disabled: findIndex(this.props.SelectedPermissions, (sp: ISelectedPermission) => { return sp.permission == perm; }) !== -1
});
}
}
return perms;
}
public render(): JSX.Element {
//Renders content
return (
<Dialog
isOpen={this.props.isOpen}
onDismiss={() => this.onClosePanel()}
title={this.props.title}
subText={this.props.subText}
>
<Dropdown label="Permission"
options={this.getPermissionTypes()}
defaultSelectedKey={this.state.currentPerm.permission}
onChange={(event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption, index?: number) => {
this.state.currentPerm.permission = option.text;
this.setState((current) => ({ ...current, currentPerm: this.state.currentPerm }));
}}>
</Dropdown>
<TextField label="Friendly Name"
defaultValue={this.state.currentPerm.freindlyName}
onChange={(event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {
this.state.currentPerm.freindlyName = newValue;
this.setState((current) => ({ ...current, currentPerm: this.state.currentPerm }));
}}>
</TextField>
<Label>Color:</Label>
<ColorPicker
color={this.state.currentPerm.color}
onChange={(event: React.FormEvent<HTMLDivElement>, color: IColor) => {
this.state.currentPerm.color = color.str;
this.setState((current) => ({ ...current, currentPerm: this.state.currentPerm }));
}}>
</ColorPicker>
<ComboBox allowFreeform={true}
label="Select Icon(or enter name of a Fabric Icon):"
options={this.getIconOptions()}
onRenderOption={this.renderIcon}
text={this.state.currentPerm.iconName}
onChange={(event: React.FormEvent<IComboBox>, option?: IComboBoxOption, index?: number, value?: string) => {
//debugger;
this.state.currentPerm.iconName = value ? value : option.text;
this.setState((current) => ({ ...current, currentPerm: this.state.currentPerm }));
}}
/>
<Label>Display:</Label>
<Icon iconName={this.state.currentPerm.iconName} style={{ color: this.state.currentPerm.color }} />
<br></br>
<br></br>
<Button onClick={() => {
this.onClosePanel();
}}>Cancel</Button>
<PrimaryButton
disabled={
this.state.currentPerm.color == null ||
this.state.currentPerm.freindlyName == null ||
this.state.currentPerm.iconName == null ||
this.state.currentPerm.permission == null
}
onClick={() => {
//debugger;
this.saveChanges();
}}
>Save</PrimaryButton>
</Dialog>
);
}
}

View File

@ -0,0 +1,73 @@
import { ISelectedPermission } from "../ISpSecurityWebPartProps";
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { IPropertyPaneField, IPropertyPaneCustomFieldProps } from "@microsoft/sp-property-pane";
import PropertyFieldSelectedPermissionsHost, { IPropertyFieldSelectedPermissionsHostProps } from './PropertyFieldSelectedPermissionsHost';
export interface IPropertyFieldSelectedPermissionsProps {
label: string;
initialValue?: Array<ISelectedPermission>;
onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void;
getSelectedPermissions: () => Array<ISelectedPermission>;
}
export interface IPropertyFieldSelectedPermissionsPropsInternal extends IPropertyPaneCustomFieldProps {
label: string;
initialValue?: Array<ISelectedPermission>;
targetProperty: string;
onRender(elem: HTMLElement): void;
onDispose(elem: HTMLElement): void;
onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void;
SelectedPermissions: Array<ISelectedPermission>;
}
class PropertyFieldSelectedPermissionsBuilder implements IPropertyPaneField<IPropertyFieldSelectedPermissionsPropsInternal> {
//Properties defined by IPropertyPaneField
public type = 1;//IPropertyPaneFieldType.Custom;
public targetProperty: string;
public properties: IPropertyFieldSelectedPermissionsPropsInternal;
//Custom properties
private label: string;
private onPropertyChange: (propertyPath: string, oldValue: any, newValue: any) => void;
private customProperties: any;
public constructor(_targetProperty: string, _properties: IPropertyFieldSelectedPermissionsPropsInternal) {
this.render = this.render.bind(this);
this.properties = _properties;
this.label = _properties.label;
this.properties.onDispose = this.dispose;
this.properties.onRender = this.render;
this.onPropertyChange = _properties.onPropertyChange;
this.customProperties = _properties.SelectedPermissions ? _properties.SelectedPermissions : [];
}
private render(elem: HTMLElement): void {
// what other args does this get? does it get ctx and a callback?
const element: React.ReactElement<IPropertyFieldSelectedPermissionsHostProps> = React.createElement(PropertyFieldSelectedPermissionsHost, {
label: this.label,
onPropertyChange: this.onPropertyChange,
SelectedPermissions: this.customProperties,
});
ReactDom.render(element, elem);
}
private dispose(elem: HTMLElement): void {
}
}
export function PropertyFieldSelectedPermissions(targetProperty: string, properties: IPropertyFieldSelectedPermissionsProps): IPropertyPaneField<IPropertyFieldSelectedPermissionsPropsInternal> {
//Create an internal properties object from the given properties
var newProperties: IPropertyFieldSelectedPermissionsPropsInternal = {
label: properties.label,
targetProperty: targetProperty,
key: targetProperty,
initialValue: properties.initialValue,
onPropertyChange: properties.onPropertyChange,
SelectedPermissions: properties.getSelectedPermissions(),
onDispose: null,
onRender: null,
};
//Calles the PropertyFieldSelectedPermissions builder object
//This object will simulate a PropertyFieldCustom to manage his rendering process
return new PropertyFieldSelectedPermissionsBuilder(targetProperty, newProperties);
}

View File

@ -0,0 +1,133 @@
import { ISelectedPermission } from "../ISpSecurityWebPartProps";
import * as React from 'react';
import { Label } from 'office-ui-fabric-react/lib/Label';
import { Button } from 'office-ui-fabric-react/lib/Button';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { DetailsList, IColumn, DetailsListLayoutMode, SelectionMode } from "office-ui-fabric-react/lib/DetailsList";
import { IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
import { SPPermission } from "@microsoft/sp-page-context";
import SelectedPermissionsPanel from "./SelectedPermissionsPanel";
export interface IPropertyFieldSelectedPermissionsHostProps {
label: string;
initialValue?: Array<ISelectedPermission>;
onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void;
SelectedPermissions: Array<ISelectedPermission>;
}
export interface IPropertyFieldSelectedPermissionsHostState {
openPanel?: boolean;
SelectedPermissions: Array<ISelectedPermission>;
}
export default class PropertyFieldSelectedPermissionsHost extends React.Component<IPropertyFieldSelectedPermissionsHostProps, IPropertyFieldSelectedPermissionsHostState> {
public panelColumns: IColumn[] = [
{
key: 'permission',
name: 'Permission',
fieldName: 'permission',
minWidth: 100,
maxWidth: 100,
isResizable: true,
onRender: (item?: any, index?: number, column?: IColumn) => {
return (
<div>
{item.permission}
</div>
);
}
},
{
key: 'freindlyName',
name: 'Name in Legend',
fieldName: 'freindlyName',
minWidth: 90,
maxWidth: 90,
isResizable: true,
onRender: (item?: any, index?: number, column?: IColumn) => {
return (
<div>
{item.freindlyName}
</div>
);
}
},
{
key: 'color',
name: 'Display',
fieldName: 'color',
minWidth: 50,
maxWidth: 50,
isResizable: false,
onRender: (item?: ISelectedPermission, index?: number, column?: IColumn) => {
return (
<Icon iconName={item.iconName} style={{ color: item.color }} />
);
}
}
];
constructor(props: IPropertyFieldSelectedPermissionsHostProps) {
super(props);
this.state = {
SelectedPermissions: this.props.SelectedPermissions,
openPanel: false
};
}
public getPermissionTypes(): IDropdownOption[] {
let perms = new Array();
for (const perm in SPPermission) {
if (typeof (SPPermission[perm]) === "object") {
perms.push({
text: perm,
key: perm
});
}
}
return perms;
}
private onOpenPanel(element?: any): void {
this.setState((current) => ({ ...current, openPanel: true }));
}
private onClosePanel(element?: any): void {
//debugger;
this.setState((current) => ({ ...current, openPanel: false }));
}
public render(): JSX.Element {
//debugger;
//This Details list Renders the short list of permissions in the panel
return (
<div style={{ marginBottom: '8px' }}>
<Label>{this.props.label}</Label>
<DetailsList
items={this.state.SelectedPermissions}
columns={this.panelColumns}
layoutMode={DetailsListLayoutMode.justified}
selectionMode={SelectionMode.none}
/>
<Button
onClick={(e) => this.onOpenPanel()}>
Edit Permissions and Colors
</Button>
<SelectedPermissionsPanel
isOpen={this.state.openPanel}
onPropertyChange={(prop, oldval, newval) => {
this.setState((current) => ({ ...current, SelectedPermissions: [...newval] }));
this.props.onPropertyChange("SelectedPermissions", this.props.SelectedPermissions, newval);
}}
closePanel={() => { this.onClosePanel(); }}
SelectedPermissions={this.props.SelectedPermissions}
/>
</div>
);
}
}

View File

@ -0,0 +1,289 @@
import { findIndex, filter, first } from "underscore";
import { ISelectedPermission } from "../ISpSecurityWebPartProps";
import * as React from 'react';
import { Label } from 'office-ui-fabric-react/lib/Label';
import { Button } from 'office-ui-fabric-react/lib/Button';
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { IconButton } from 'office-ui-fabric-react/lib/Button';
import { DetailsList, IColumn, DetailsListLayoutMode, SelectionMode, Selection } from "office-ui-fabric-react/lib/DetailsList";
import { Dropdown, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
import { CommandBar } from "office-ui-fabric-react/lib/CommandBar";
import { SPPermission } from "@microsoft/sp-page-context";
import ColorIconSelectorDialog from "./ColorIconSelectorDialog";
import { disableBodyScroll } from "@uifabric/utilities";
export interface ISelectedPemissionPanelProps {
isOpen: boolean;
onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void;
closePanel(): void;
SelectedPermissions: Array<ISelectedPermission>;
}
export interface ISelectedPemissionPanelState {
SelectedPermissions: Array<ISelectedPermission>;
CurrentlySelectedPermission?: ISelectedPermission;
isColorIconSelecorDialogOpen: boolean;
}
export default class SelectedPermissionsPanel extends React.Component<ISelectedPemissionPanelProps, ISelectedPemissionPanelState> {
private selection: Selection;
private columns: IColumn[] = [
{
key: 'permission',
name: 'Permission',
fieldName: 'permission',
minWidth: 150,
maxWidth: 150,
isResizable: true,
onRender: (item?: any, index?: number, column?: IColumn) => {
return (
<Dropdown
options={this.getPermissionTypes()}
defaultSelectedKey={item.permission}
onChange={(event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption, ix?: number) => {
var sps = this.state.SelectedPermissions;
item.permission = option.text;
this.setState((current) => ({ ...current, SelectedPermissions: [...this.state.SelectedPermissions] }));
}}>
</Dropdown>
);
}
},
{
key: 'freindlyName',
name: 'Name in Legend',
fieldName: 'freindlyName',
minWidth: 150,
maxWidth: 150,
isResizable: true
},
{
key: 'color',
name: 'Display',
fieldName: 'color',
minWidth: 100,
maxWidth: 100,
isResizable: true,
onRender: (item?: ISelectedPermission, index?: number, column?: IColumn) => {
return (
<div>
<Icon iconName={item.iconName} style={{ color: item.color }} />
<Button onClick={(e) => {
this.setState((current) => ({
...current,
isColorIconSelecorDialogOpen: true,
CurrentlySelectedPermission: item
}));
}}>Edit Permission</Button>
</div>
);
}
},
{
key: 'commands',
name: '',
fieldName: 'color',
minWidth: 50,
maxWidth: 50,
isResizable: false,
onRender: (item?: any, index?: number, column?: IColumn) => {
return (
<div>
<IconButton
iconProps={{ iconName: 'Up', }}
style={{ display: index === 0 ? "none" : "normal" }}
onClick={(e) => {
this.moveColumnUp(item);
}}>
</IconButton>
<IconButton
iconProps={{ iconName: 'Down', }}
style={{ display: index === this.state.SelectedPermissions.length - 1 ? "none" : "normal" }}
onClick={(e) => {
this.moveColumnDown(item);
}}>
</IconButton>
<IconButton
iconProps={{ iconName: 'Delete', }}
onClick={(e) => {
this.removeColumn(item);
}}>
</IconButton>
</div>
);
}
},
];
constructor(props: ISelectedPemissionPanelProps) {
super(props);
this.selection = new Selection({
onSelectionChanged: () => console.log("onSelectionChanged...")
});
this.state = {
SelectedPermissions: this.props.SelectedPermissions,
isColorIconSelecorDialogOpen: false,
};
}
public getPermissionTypes(): IDropdownOption[] {
let perms = new Array<IDropdownOption>();
for (const perm in SPPermission) {
if (typeof (SPPermission[perm]) === "object") {
perms.push({
text: perm,
key: perm,
disabled: findIndex(this.state.SelectedPermissions, (sp: ISelectedPermission) => { return sp.permission == perm; }) !== -1
});
}
}
return perms;
}
// private addColumn(): void {
// let unusedPermission = first(filter(this.getPermissionTypes(), (pt) => { return !pt.disabled }));
// if (unusedPermission) {
// const col: ISelectedPermission = {
// "permission": unusedPermission.text,
// "freindlyName": unusedPermission.text,
// color: "FFFFFF",
// iconName: "Blocked"
// };
// var sp = this.state.SelectedPermissions;
// sp.push(col);
// this.setState((current) => ({ ...current, SelectedPermissions: [...sp] }));
// }
// }
private removeColumn(column: ISelectedPermission): void {
var sps = filter(this.state.SelectedPermissions, (o: ISelectedPermission) => { return o.permission !== column.permission; });
this.setState((current) => ({ ...current, SelectedPermissions: [...sps] }));
}
private removeAllColumns(): void {
this.setState((current) => ({ ...current, SelectedPermissions: [] }));
}
private moveColumnUp(column: ISelectedPermission): void {
var sps: ISelectedPermission[] = this.state.SelectedPermissions;
const index = findIndex(sps, (sp: ISelectedPermission) => { return sp.permission == column.permission; });
if (index != -1) {
sps[index] = sps.splice(index - 1, 1, sps[index])[0];
this.setState((current) => ({ ...current, SelectedPermissions: [...sps] }));
}
}
private moveColumnDown(column: ISelectedPermission): void {
var sps: ISelectedPermission[] = this.state.SelectedPermissions;
const index = findIndex(sps, (sp: ISelectedPermission) => { return sp.permission == column.permission; });
if (index != -1) {
sps[index] = sps.splice(index + 1, 1, sps[index])[0];
this.setState((current) => ({ ...current, SelectedPermissions: [...sps] }));
}
}
private saveChanges(): void {
if (this.props.onPropertyChange) {
this.props.onPropertyChange("SelectedPermissions", this.props.SelectedPermissions, this.state.SelectedPermissions);
this.onClosePanel();
}
}
private onOpenPanel(element?: any): void {
this.setState((current) => ({ ...current, openPanel: true }));
}
private onClosePanel(element?: any): void {
this.props.closePanel();
}
public render(): JSX.Element {
//Renders content
return (
<Panel
isOpen={this.props.isOpen} hasCloseButton={true}
onDismiss={() => this.onClosePanel()}
isLightDismiss={true} type={PanelType.largeFixed}
headerText="Select Permissions" >
<Label>The grid will display the color of the first match, so list permissions from most restricted to least restricted (i.e. manageLists, then deleteListItems, then viewListItems)</Label>
<CommandBar items={[{
key: "AddColumns",
name: "Add a Permission",
icon: "Add",
onClick: () => {
this.setState((current) => ({
...current,
isColorIconSelecorDialogOpen: true,
CurrentlySelectedPermission: { color: null, freindlyName: null, iconName: null, permission: null }
}));
}
},
{
key: "ClearAllColums",
name: "Remove All Permissions",
canCheck: true,
icon: "Delete",
onClick: () => {
this.removeAllColumns();
}
},
{
key: "save",
name: "Save",
canCheck: true,
icon: "Save",
onClick: () => {
this.saveChanges();
}
}
]} />
{this.state.isColorIconSelecorDialogOpen &&
<ColorIconSelectorDialog
isOpen={this.state.isColorIconSelecorDialogOpen}
SelectedPermissions={this.state.SelectedPermissions}
title={`Edit Icon and color for ${this.state.CurrentlySelectedPermission.permission}`}
subText={`Edit Icon and color for ${this.state.CurrentlySelectedPermission.permission}`}
currentPerm={this.state.CurrentlySelectedPermission}
closePanel={() => {
this.setState((current) => ({ ...current, isColorIconSelecorDialogOpen: false }));
}}
onPermissionChange={(perm: ISelectedPermission) => {
//debugger;
var sps = this.state.SelectedPermissions;
const idx = findIndex(sps, (sp: ISelectedPermission) => { return sp.permission == perm.permission; });
if (idx === -1) {
sps.push(perm);
} else {
sps[idx] = perm;
}
this.setState((current) => ({ ...current, SelectedPermissions: [...sps] }));
}}
/>
}
<DetailsList
items={this.state.SelectedPermissions}
columns={this.columns}
selectionMode={SelectionMode.single}
layoutMode={DetailsListLayoutMode.justified}
selection={this.selection}
/>
</Panel>
);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,47 @@
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.2/MicrosoftTeams.schema.json",
"manifestVersion": "1.2",
"packageName": "SPSecurity",
"id": "41e37f03-2ea8-4f19-b77a-f2121a1e7c45",
"version": "0.1",
"developer": {
"name": "SPFx + Teams Dev",
"websiteUrl": "https://products.office.com/en-us/sharepoint/collaboration",
"privacyUrl": "https://privacy.microsoft.com/en-us/privacystatement",
"termsOfUseUrl": "https://www.microsoft.com/en-us/servicesagreement"
},
"name": {
"short": "SPSecurity"
},
"description": {
"short": "Security Grid Display",
"full": "Security Grid Display"
},
"icons": {
"outline": "tab20x20.png",
"color": "tab96x96.png"
},
"accentColor": "#004578",
"configurableTabs": [
{
"configurationUrl": "https://{teamSiteDomain}{teamSitePath}/_layouts/15/TeamsLogon.aspx?SPFX=true&dest={teamSitePath}/_layouts/15/teamshostedapp.aspx%3FopenPropertyPane=true%26teams%26componentId=41e37f03-2ea8-4f19-b77a-f2121a1e7c45",
"canUpdateConfiguration": true,
"scopes": [
"team"
]
}
],
"validDomains": [
"*.login.microsoftonline.com",
"*.sharepoint.com",
"*.sharepoint-df.com",
"spoppe-a.akamaihd.net",
"spoprod-a.akamaihd.net",
"resourceseng.blob.core.windows.net",
"msft.spoppe.com"
],
"webApplicationInfo": {
"resource": "https://{teamSiteDomain}",
"id": "00000003-0000-0ff1-ce00-000000000000"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 933 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,8 +1,21 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"lib"
],
"compilerOptions": {
"moduleResolution": "node",
"outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "commonjs",
"module": "esnext",
"jsx": "react",
"declaration": true,
"sourceMap": true,

View File

@ -1,3 +1,30 @@
{
"rulesDirectory": "./config"
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"variable-name": false,
"whitespace": false
}
}