This commit is contained in:
Russell gove 2021-07-02 14:07:44 -04:00
parent a3c0fc3b33
commit 2f18b3f40f
49 changed files with 21407 additions and 0 deletions

View File

@ -0,0 +1,25 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": false,
"environment": "spo",
"version": "1.10.0",
"libraryName": "r-fx-portal",
"libraryId": "955d006f-7cc7-461f-8c5d-29593aebac61",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,129 @@
# Private Folder Manager
## Summary
This sample provides a webpart that can be used to manage Document Libraries with Secured Subfolders. The use case this was developed for is a Request for Proposal site. A Document library is created for each RFP and subfolders are created within that Library for each external supplier invited to participate in that RFP.
The application manages all the security groups set up for the various libraries and folders so that suppliers only see RFP's they were ivited to participate in and only thier folders.
![The main panel](assets/Home Screen.png)
## Compatibility
![SPFx 1.12.1](https://img.shields.io/badge/SPFx-1.12.1-green.svg)
![Node.js LTS v14 | LTS v12 | LTS v10](https://img.shields.io/badge/Node.js-LTS%20v14%20%7C%20LTS%20v12%20%7C%20LTS%20v10-green.svg)
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
![Teams N/A: Untested with Microsoft Teams](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg "Untested with Microsoft Teams")
![Workbench Local | Hosted](https://img.shields.io/badge/Workbench-Local%20%7C%20Hosted-green.svg)
> Don't worry if you're unsure about the compatibility matrix above. We'll verify it when we approve the PR.
>
> If using an older version of SPFx, update the SPFx and Node.js compatibility tag accordingly:
> SPFx 1.11
> ![SPFx 1.11](https://img.shields.io/badge/SPFx-1.11.0-green.svg)
> ![Node.js LTS 10.x](https://img.shields.io/badge/Node.js-LTS%2010.x-green.svg)
>
> SPFx 1.4.1
> ![SPFx 1.4.1](https://img.shields.io/badge/SPFx-1.4.1-green.svg)
> ![Node.js LTS 6.x | LTS 8.x](https://img.shields.io/badge/Node.js-LTS%206.x%20%7C%20LTS%208.x-green.svg)
>
> If you built this sample specifically for SharePoint 2016, or SharePoint 2019 support, update the SharePoint compatibility tag accordingly:
> ![SharePoint 2019 | Online](https://img.shields.io/badge/SharePoint-2019%20%7C%20Online-yellow.svg)
> ![SharePoint 2016 | 2019 | Online](https://img.shields.io/badge/SharePoint-2016%20%7C%202019%20%7C%20Online-green.svg)
> If you know your web part only works on the hosted workbench, you can use this for the workbench compatibility tag:
> ![Workbench Hosted: Does not work with local workbench](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Does not work with local workbench")
>
> If you specifically built and tested this web part to work with Teams, use this for the Teams compatibility tag:
> ![Teams Yes: Designed for Microsoft Teams](https://img.shields.io/badge/Teams-Yes-green.svg "Designed for Microsoft Teams")
> And if you know for sure that it is NOT compatible with Teams, use this:
> ![Teams No: Not designed for Microsoft Teams](https://img.shields.io/badge/Teams-No-red.svg "Not designed for Microsoft Teams")
>
> DELETE THIS PARAGRAPH BEFORE SUBMITTING
## Applies to
* [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
* [Microsoft 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment)
> Update accordingly as needed.
> DELETE THIS PARAGRAPH BEFORE SUBMITTING
## Solution
> We use this section to recognize and promote your contributions. Please provide one author per line -- even if you worked together on it.
> We'll only use the info you provided here. Make sure to include your full name, not just your GitHub username.
> Provide a link to your GitHub profile to help others find more cool things you have done.
> If you provide a link to your Twitter profile, we'll promote your contribution on social media.
>
> DELETE THE TEXT ABOVE BEFORE SUBMITTING
Solution|Author(s)
--------|---------
react Private Folder Manager | [Russell Gove](https://github.com/russgove) ([@russgove](https://twitter.com/russgove))
## Version history
Version|Date|Comments
-------|----|--------
1.0|July 2, 2021|Initial release
## Prerequisites
> Any special pre-requisites? Include any lists, permissions, offerings to the demo gods, or whatever else needs to be done for this web part to work.
>
> Please describe the steps to configure the pre-requisites. Feel free to add screen shots, but make sure that there is a text description of the steps to perform.
>
> DELETE THE TEXT ABOVE BEFORE SUBMITTING
## Minimal Path to Awesome
* Clone this repository
* in the command line run:
* `npm install`
* `gulp serve`
> Include any additional steps as needed.
> DELETE THIS PARAGRAPH BEFORE SUBMITTING
## Features
Description of the web part with possible additional details than in short summary.
This Web Part illustrates the following concepts on top of the SharePoint Framework:
* topic 1
* topic 2
* topic 3
> Note that better pictures and documentation will increase the sample usage and the value you are providing for others. Thanks for your submissions in advance! You rock ❤.
> DELETE THIS PARAGRAPH BEFORE SUBMITTING
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
## Help
We do not support samples, but we this community is always willing to help, and we want to improve these samples. We use GitHub to track issues, which makes it easy for community members to volunteer their time and help resolve issues.
If you encounter any issues while using this sample, [create a new issue](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=bug-report.yml&sample=YOUR-SOLUTION-NAME&authors=@YOURGITHUBUSERNAME&title=YOUR-SOLUTION-NAME%20-%20).
For questions regarding this sample, [create a new question](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=question.yml&sample=YOUR-SOLUTION-NAME&authors=@YOURGITHUBUSERNAME&title=YOUR-SOLUTION-NAME%20-%20).
Finally, if you have an idea for improvement, [make a suggestion](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=suggestion.yml&sample=YOUR-SOLUTION-NAME&authors=@YOURGITHUBUSERNAME&title=YOUR-SOLUTION-NAME%20-%20).
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/YOUR-SOLUTION-NAME" />
This package produces the following:
* lib/* - intermediate-stage commonjs build artifacts
* dist/* - the bundled script, along with other resources
* deploy/* - all resources which should be uploaded to a CDN.
### Build options
gulp clean - TODO
gulp test - TODO
gulp serve - TODO
gulp bundle - TODO
gulp package-solution - TODO

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -0,0 +1,29 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"vendor-maintenance-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/vendorMaintenance/VendorMaintenanceWebPart.js",
"manifest": "./src/webparts/vendorMaintenance/VendorMaintenanceWebPart.manifest.json"
}
]
},
"request-maintenance-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/requestMaintenance/RequestMaintenanceWebPart.js",
"manifest": "./src/webparts/requestMaintenance/RequestMaintenanceWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"VendorMaintenanceWebPartStrings": "lib/webparts/vendorMaintenance/loc/{locale}.js",
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js",
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js",
"RequestMaintenanceWebPartStrings": "lib/webparts/requestMaintenance/loc/{locale}.js"
}
}

View File

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

View File

@ -0,0 +1,7 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "r-fx-portal",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "r-fx-portal-client-side-solution",
"id": "955d006f-7cc7-461f-8c5d-29593aebac61",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"isDomainIsolated": false
},
"paths": {
"zippedPackage": "solution/r-fx-portal.sppkg"
}
}

View File

@ -0,0 +1,12 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"https": true,
"serveConfigurations": {
"default": {
"pageUrl": "https://russellwgove.sharepoint.com/sites/ReactPrivateLibraries/_layouts/15/workbench.aspx"
}
}
}

View File

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

View File

@ -0,0 +1,7 @@
'use strict';
const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
build.initialize(require('gulp'));

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,54 @@
{
"name": "r-fx-portal",
"version": "0.0.1",
"private": true,
"main": "lib/index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/applicationinsights-react-js": "3.0.3",
"@microsoft/applicationinsights-web": "2.5.8",
"@microsoft/sp-core-library": "1.10.0",
"@microsoft/sp-lodash-subset": "1.10.0",
"@microsoft/sp-office-ui-fabric-core": "1.10.0",
"@microsoft/sp-property-pane": "1.10.0",
"@microsoft/sp-webpart-base": "1.10.0",
"@pnp/common": "2.0.6",
"@pnp/logging": "2.0.6",
"@pnp/odata": "2.0.6",
"@pnp/polyfill-ie11": "2.0.2",
"@pnp/sp": "2.0.6",
"@pnp/spfx-controls-react": "1.20.0",
"@pnp/spfx-property-controls": "1.20.0",
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-csv": "1.1.1",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"date-fns": "2.16.1",
"office-ui-fabric-react": "6.189.2",
"react": "16.8.5",
"react-csv": "2.0.3",
"react-dom": "16.8.5"
},
"resolutions": {
"@types/react": "16.8.8"
},
"devDependencies": {
"@microsoft/sp-build-web": "1.10.0",
"@microsoft/sp-tslint-rules": "1.10.0",
"@microsoft/sp-module-interfaces": "1.10.0",
"@microsoft/sp-webpart-workbench": "1.10.0",
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
"gulp": "~3.9.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2"
}
}

View File

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

View File

@ -0,0 +1,11 @@
export interface IActivity {
userEmail: string;
userDisplayName: string;
driveItemType: string;
driveItemName: string;
driveItemParentReference: string;
activityRecordedTime:Date;
action:string;
}

View File

@ -0,0 +1,8 @@
export interface IDrive {
description: string;
id: string;
name: string;
webUrl: string;
driveType: string;
}

View File

@ -0,0 +1,17 @@
export interface IRFx {
title: string;
closingDate:Date;
contractSpecialist:{
EMail:string
};
contractSpecialistId:number;
id:number; //the id of the listitem that holds the doclib title and securitygroup name
// internalSecurityGroupId:number;members
libraryMembersGroupId:number;
//externalSecurityGroupId:number;;visitors
libraryVisitorsGroupId:number;
libraryOwnersGroupId:number;
description:string;
}

View File

@ -0,0 +1,10 @@
export interface IRFxFolder {
title: string;
id:number; //the id of the listitem that holds the doclib title and securitygroup name
folderMembersGroupId:number;
folderVisitorsGroupId:number;
rfxId:number;
}

View File

@ -0,0 +1,7 @@
export interface IVendor {
title: string;
membersGroup:string;
id:number; //the id of the listitem that holds the doclib title and securitygroup name
}

View File

@ -0,0 +1,59 @@
import { SeverityLevel } from '@microsoft/applicationinsights-web';
import { ApplicationInsights } from '@microsoft/applicationinsights-web';
import { ReactPlugin } from '@microsoft/applicationinsights-react-js';
import {
ILogEntry,
ILogListener,
LogLevel
} from "@pnp/logging";
class CustomListener implements ILogListener {
private appInsights: ApplicationInsights;
private instrumentationKey: string;
constructor(instrumentationKey: string) {
this.instrumentationKey = instrumentationKey;
this.appInsights = this.getApplicationInsights();
}
public log(entry: ILogEntry): void {
if (entry.level == LogLevel.Error)
this.appInsights.trackException({
error: new Error(entry.message),
severityLevel: SeverityLevel.Error
});
else if (entry.level == LogLevel.Warning)
this.appInsights.trackException({
error: new Error(entry.message),
severityLevel: SeverityLevel.Warning
});
else if (entry.level == LogLevel.Info)
this.appInsights.trackException({
error: new Error(entry.message),
severityLevel: SeverityLevel.Information
});
else
this.appInsights.trackException({
error: new Error(entry.message),
severityLevel: SeverityLevel.Verbose
});
this.appInsights.trackTrace({ message: entry.message });
}
private getApplicationInsights(): ApplicationInsights {
debugger;
const reactPlugin = new ReactPlugin();
const applicationInsights = new ApplicationInsights({
config: {
instrumentationKey: this.instrumentationKey,
extensions: [reactPlugin]
}
});
applicationInsights.loadAppInsights();
return applicationInsights;
}
}
export default CustomListener;

View File

@ -0,0 +1,218 @@
import { sp, SPHttpClient } from "@pnp/sp";
import { ISiteGroup, ISiteGroupInfo } from '@pnp/sp/site-groups';
import { autobind } from 'office-ui-fabric-react/lib/Utilities';
import { IViewInfo } from '@pnp/sp/views';
import { IRoleDefinition, IRoleDefinitionInfo } from '@pnp/sp/security';
import { escape, findIndex, find } from '@microsoft/sp-lodash-subset';
import { IItem } from "@pnp/sp/items";
import { IRFxFolder } from '../models/IRFxFolder';
export default class RFXUtilities {
public roleDefs: IRoleDefinitionInfo[];
/**
* Sets the parent of a group to another group using JSOM calls (this is not supported in rest)
* @param groupId -- the ID of the group whose parent will be changed
* @param ownerGroupId -- the id of the group that will become the parent
*/
public static async setGroupOwner(groupId: number, ownerGroupId: number): Promise<void> {
const client = new SPHttpClient();
await Promise.all([sp.web.select("Url").get(), sp.site.select("Id").get()])
.then(async (siteData) => {
const siteObj = siteData[0];
const siteId = siteData[1];
const endpoint = siteObj.Url + `/_vti_bin/client.svc/ProcessQuery`;
const body = `<Request AddExpandoFieldTypeSuffix="true" SchemaVersion="15.0.0.0" LibraryVersion="15.0.0.0" ApplicationName=".NET Library" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009">
<Actions>
<SetProperty Id="1" ObjectPathId="2" Name="Owner">
<Parameter ObjectPathId="3" />
</SetProperty>
<Method Name="Update" Id="4" ObjectPathId="2" />
</Actions>
<ObjectPaths>
<Identity Id="2" Name="740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:${siteId.Id}:g:${groupId}" />
<Identity Id="3" Name="740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:${siteId.Id}:g:${ownerGroupId}" />
</ObjectPaths>
</Request>`;
await client.post(endpoint, {
headers: {
"content-type": "text/xml"
},
body: body
})
.then((response) => {
return response.json().then((r) => {
if (r[0].ErrorInfo) {
return Promise.reject(r[0].ErrorInfo.ErrorMessage);
} else {
return Promise.resolve();
}
});
});
});
}
/**
* Gets the ID of a roledefinition with the given name
*
* @public
* @param {string} roleDefinitionName The name of the roleDefinition whose ID we want to get
* @returns {Promise<number>} The ID if the roleDefinition. If not found this will be -1
* @memberof DocLibSecurity
*/
public static getRoledefId(roleDefinitionName: string, roleDefinitions: Array<IRoleDefinitionInfo>): number {
for (const def of roleDefinitions) {
if (def.Name === roleDefinitionName) {
return def.Id;
}
}
return -1;
}
/**
* Grants a group access to a library
*
* @public
* @param {string} libraryName The name of the library to grant access to
* @param {string} groupName The name of the group to gtrant access for
* @param {string} roleName The role to assign the group to on this library
* @returns {Promise<any>}
* @memberof DocLibSecurity
*/
public static async grantGroupAccessToLibrary(libraryName: string, groupId: number, roleName: string, roleDefinitions: Array<IRoleDefinitionInfo>): Promise<any> {
const roleDefId = await RFXUtilities.getRoledefId(roleName, roleDefinitions);
if (roleDefId === -1) {
return Promise.reject(`"Role ${roleName} not found`);
}
return sp.web.lists.getByTitle(libraryName).roleAssignments.add(groupId, roleDefId);
}
public static async removeAccessToLibrary(libraryName: string, principalId: number, roleName: string, roleDefinitions: Array<IRoleDefinitionInfo>): Promise<any> {
const roleDefId = await RFXUtilities.getRoledefId(roleName, roleDefinitions);
if (roleDefId === -1) {
return Promise.reject(`"Role ${roleName} not found`);
}
return sp.web.lists.getByTitle(libraryName).roleAssignments.remove(principalId, roleDefId);
}
/**
* Determinse if a SiteGroup with the given name exists
*
* @public
* @param {string} groupName The name of the SiteGroup we want to check
* @returns {Promise<boolean>} True if SiteGroup exists , otherwise false
* @memberof DocLibSecurity
*/
public static doesGroupExist(groupName: string): Promise<boolean> {
return sp.web.siteGroups.getByName(groupName).get()
.then((list) => {
return true;
})
.catch((e) => {
return false;
});
}
// /**
// * Grants a group access to the site
// *
// * @public
// * @param {string} groupName The group name to be granted access
// * @param {string} roleName The role to assign the group to on this site
// * @returns {Promise<any>}
// * @memberof DocLibSecurity
// */
// // public static async grantNewGroupAccessToSite(groupName: string, roleName: string, roleDefinitions: Array<IRoleDefinitionInfo>): Promise<any> {
// // const roleDefId = await this.getRoledefId(roleName, roleDefinitions);
// // const grp = await sp.web.siteGroups.getByName(groupName).get();
// // return sp.web.roleAssignments.add(grp.Id, roleDefId);
// // }
public static async grantGroupIdAccessToSite(groupId: number, roleName: string, roleDefinitions: Array<IRoleDefinitionInfo>): Promise<any> {
const roleDefId = await this.getRoledefId(roleName, roleDefinitions);
return sp.web.roleAssignments.add(groupId, roleDefId);
}
/**
* Navigates to the default view of the selected lbrary
*
* @public
* @param {string} title -- the Title of the doclib to open
* @memberof DocLibSecurity
*/
public static linkToLibrary(title: string): void {
sp.web.lists.getByTitle(title).defaultView.get()
.then((view: IViewInfo) => {
window.location.pathname = view.ServerRelativeUrl;
});
}
public static linkToFolder(folder: IRFxFolder, rfxListName: string): void {
let ifrx = sp.web.lists.getByTitle(rfxListName).items.getById(folder.rfxId).get()
.then((rfx) => {
sp.web.lists.getByTitle(rfx["Title"]).rootFolder.folders.getByName(folder.title).get()
.then((item) => {
debugger;
window.location.pathname = item.ServerRelativeUrl;
});
});
}
/**
* Naviates to the Group Maintenance page for the selected Group
*
* @public
* @param {string} groupName The name of the Group
* @memberof DocLibSecurity
*/
public static linkToGroup(groupName: string, webServerRelativeUrl: string): void {
sp.web.siteGroups.getByName(groupName).get()
.then((grp: ISiteGroupInfo) => {
let newUrl = `${window.location.origin}${webServerRelativeUrl}/_layouts/15/people.aspx?MembershipGroupId=${grp.Id}`;
window.location.href = newUrl;
});
}
/**
* Naviates to the Group Maintenance page for the selected Group
*
* @public
* @param {string} groupName The name of the Group
* @memberof DocLibSecurity
*/
public static linkToGroupById(groupId: number, webServerRelativeUrl: string): void {
let newUrl = `${window.location.origin}${webServerRelativeUrl}/_layouts/15/people.aspx?MembershipGroupId=${groupId}`;
window.location.href = newUrl;
}
/**
* Delets a group with the given name
*
* @public
* @param {string} groupName the name of the group to remove
* @returns {Promise<void>}
* @memberof DocLibSecurity
*/
public static deletegroup(groupName: string): Promise<void> {
return sp.web.siteGroups.getByName(groupName).get()
.then((grp: ISiteGroupInfo) => {
sp.web.siteGroups.removeById(grp.Id);
});
}
public static getSiteGroupName(siteGroupId: number, siteGroups: Array<ISiteGroupInfo>): string {
let sg: ISiteGroupInfo = find(siteGroups, (sgx) => {
return sgx.Id === siteGroupId;
});
if (sg) {
return sg.Title;
}
else { return (`*${siteGroupId}*`); }
}
}

View File

@ -0,0 +1,47 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "bad86476-e6ad-4162-92a5-4663ed8bb68d",
"alias": "RequestMaintenanceWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportedHosts": [
"SharePointWebPart"
],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": {
"default": "Other"
},
"title": {
"default": "RequestMaintenance"
},
"description": {
"default": "RequestMaintenance description"
},
"officeFabricIconFontName": "Page",
"properties": {
"rfxListTitle": "RFXs",
"rfxFoldersListTitle": "RFXFolders",
"archiveLibraryTitle": "Archive",
"enablePrivateFolders": true,
"roleDefinitionForLibraryMembersGroupOnSite": "Read",
"roleDefinitionForLibraryMembersGroupOnLibrary": "Contribute",
"roleDefinitionForLibraryMembersGroupOnFolder": "Contribute",
"roleDefinitionForLibraryVisitorsGroupOnSite": "Read",
"roleDefinitionForLibraryVisitorsGroupOnLibrary": "Read",
"roleDefinitionForLibraryVisitorsGroupOnFolder": "Read",
"roleDefinitionForFolderMembersOnSite": "Read",
"roleDefinitionForFolderMembersOnLibrary": "Read",
"roleDefinitionForFolderMembersOnFolder": "Edit",
"roleDefinitionForFolderVisitorsOnSite": "Read",
"roleDefinitionForFolderVisitorsOnLibrary": "Read",
"roleDefinitionForFolderVisitorsOnFolder": "Read"
}
}]
}

View File

@ -0,0 +1,291 @@
import CustomListener from "../../utilities/CustomListener";
import { IRequestMaintenanceProps } from './components/IRequestMaintenanceProps';
import RequestMaintenance from './components/RequestMaintenance';
import { IPropertyPaneConfiguration, PropertyPaneDropdown, PropertyPaneTextField, PropertyPaneToggle } from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { setup as pnpSetup } from "@pnp/common";
import { ConsoleListener, Logger, LogLevel } from "@pnp/logging";
import { sp } from "@pnp/sp";
import { IRoleDefinitionInfo } from '@pnp/sp/security';
import { ISiteGroupInfo } from '@pnp/sp/site-groups';
import * as React from 'react';
import * as ReactDom from 'react-dom';
import * as strings from 'RequestMaintenanceWebPartStrings';
import "@pnp/sp/security";
import "@pnp/sp/site-groups/web";
import "@pnp/sp/site-groups/web";
import "@pnp/sp/site-users";
import "@pnp/sp/webs";
//mport { ISiteUser } from '@pnp/sp/site-users';
export interface IRequestMaintenanceWebPartProps {
rfxListTitle: string; // the name of the list that holds all the RFX Info
rfxFoldersListTitle: string;// the name of the list that holds all the RFX folder Info
instrumentationKey: string; // app insights key
archiveLibraryTitle: string;
enablePrivateFolders: boolean;
roleDefinitionForLibraryMembersGroupOnLibrary: string;// each new RFX library has an group created for internal users. What role should that group have on trghe library (Contribute)
roleDefinitionForLibraryMembersGroupOnSite: string; // each new RFX library has an group created for internal users. What role should that group have on trghe site (READ)
roleDefinitionForLibraryMembersGroupOnFolder: string; // each new RFX library has an group created for internal users. What role should that group have on trghe site (READ)
roleDefinitionForLibraryVisitorsGroupOnLibrary: string;// each new RFX library has an group created for external users. What role should that group have on trghe library (Contribute)
roleDefinitionForLibraryVisitorsGroupOnSite: string; // each new RFX library has an group created for external users. What role should that group have on trghe site (READ)
roleDefinitionForLibraryVisitorsGroupOnFolder: string; // each new RFX library has an group created for external users. What role should that group have on trghe site (READ)
roleDefinitionForFolderMembersOnFolder: string; // each new RFX library has an group created for external users. What role should that group have on trghe site (READ)
roleDefinitionForFolderMembersOnLibrary: string; // each new RFX library has an group created for external users. What role should that group have on trghe site (READ)
roleDefinitionForFolderMembersOnSite: string; // each new RFX library has an group created for external users. What role should that group have on trghe site (READ)
roleDefinitionForFolderVisitorsOnFolder: string; // each new RFX library has an group created for external users. What role should that group have on trghe site (READ)
roleDefinitionForFolderVisitorsOnLibrary: string; // each new RFX library has an group created for external users. What role should that group have on trghe site (READ)
roleDefinitionForFolderVisitorsOnSite: string; // each new RFX library has an group created for external users. What role should that group have on trghe site (READ)
allowGroupNameChanges: boolean;
}
export default class RequestMaintenanceWebPart extends BaseClientSideWebPart<IRequestMaintenanceWebPartProps> {
private vistorsGroup: ISiteGroupInfo;
private ownersGroup: ISiteGroupInfo;
private membersGroup: ISiteGroupInfo;
private roleDefinitions: IRoleDefinitionInfo[];
private roleDefinitionsDropdownDisabled: boolean;
// private currentUser:ISiteUser;
public render(): void {
const element: React.ReactElement<IRequestMaintenanceProps> = React.createElement(
RequestMaintenance,
{
siteOwnersGroup: this.ownersGroup, // Site owners groupw will be given full contyrol on new libraros
rfxListTitle: this.properties.rfxListTitle, // the name of the list that holds all the RFX Info
rfxFoldersListTitle: this.properties.rfxFoldersListTitle, // the name of the list that holds all the RFX Info
webServerRelativeUrl: this.context.pageContext.web.serverRelativeUrl,
archiveLibraryTitle: this.properties.archiveLibraryTitle,
enablePrivateFolders: this.properties.enablePrivateFolders,
roleDefinitions: this.roleDefinitions,
roleDefinitionForLibraryMembersGroupOnSite: this.properties.roleDefinitionForLibraryMembersGroupOnSite, // each new RFX library has an group created for internal users. What role should that group have on trghe site (READ)
roleDefinitionForLibraryMembersGroupOnLibrary: this.properties.roleDefinitionForLibraryMembersGroupOnLibrary,// each new RFX library has an group created for internal users. What role should that group have on trghe library (Contribute)
roleDefinitionForLibraryMembersGroupOnFolder: this.properties.roleDefinitionForLibraryMembersGroupOnFolder,// each new RFX library has an group created for internal users. What role should that group have on trghe library (Contribute)
roleDefinitionForLibraryVisitorsGroupOnSite: this.properties.roleDefinitionForLibraryVisitorsGroupOnSite, // each new RFX library has an group created for external users. What role should that group have on trghe site (READ)
roleDefinitionForLibraryVisitorsGroupOnLibrary: this.properties.roleDefinitionForLibraryVisitorsGroupOnLibrary,// each new RFX library has an group created for external users. What role should that group have on trghe library (Contribute)
roleDefinitionForLibraryVisitorsGroupOnFolder: this.properties.roleDefinitionForLibraryVisitorsGroupOnFolder,// each new RFX library has an group created for external users. What role should that group have on trghe library (Contribute)
roleDefinitionForFolderVisitorsOnFolder: this.properties.roleDefinitionForFolderVisitorsOnFolder, // each new RFX library has an group created for external users. What role should that group have on trghe site (READ)
roleDefinitionForFolderVisitorsOnLibrary: this.properties.roleDefinitionForFolderVisitorsOnLibrary, // each new RFX library has an group created for external users. What role should that group have on trghe site (READ)
roleDefinitionForFolderVisitorsOnSite: this.properties.roleDefinitionForFolderVisitorsOnSite, // each new RFX library has an group created for external users. What role should that group have on trghe site (READ)
roleDefinitionForFolderMembersOnFolder: this.properties.roleDefinitionForFolderMembersOnFolder, // each new RFX library has an group created for external users. What role should that group have on trghe site (READ)
roleDefinitionForFolderMembersOnLibrary: this.properties.roleDefinitionForFolderMembersOnLibrary,
roleDefinitionForFolderMembersOnSite: this.properties.roleDefinitionForFolderMembersOnSite,
allowGroupNameChanges: this.properties.allowGroupNameChanges
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
public onInit(): Promise<any> {
//sessionStorage.setItem("spfx-debug", ""); //// REMOVE THIS
return super.onInit().then(_ => {
pnpSetup({
spfxContext: this.context
});
Logger.activeLogLevel = LogLevel.Warning;
Logger.subscribe(new ConsoleListener());
if (this.properties.instrumentationKey) {
Logger.subscribe(new CustomListener(this.properties.instrumentationKey));
}
let groupsPromise = this.getGroups();
let roledefPromise = this.getRoleDefinitions();
return Promise.all([groupsPromise, roledefPromise]);
});
}
public getRoleDefinitions(): Promise<void> {
return sp.site.rootWeb.roleDefinitions.get().then((rds) => {
this.roleDefinitions = rds;
});
}
public getGroups(): Promise<any> {
let promises: Array<Promise<any>> = [];
promises.push(sp.web.associatedVisitorGroup()
.then((g) => {
this.vistorsGroup = g;
})
.catch((e) => {
debugger;
}));
// Gets the associated members group of a web
promises.push(sp.web.associatedMemberGroup()
.then((g) => {
this.membersGroup = g;
})
.catch((e) => {
debugger;
}));
// Gets the associated owners group of a web
promises.push(sp.web.associatedOwnerGroup()
.then((g) => {
this.ownersGroup = g;
})
.catch((e) => {
debugger;
}));
return Promise.all(promises);
}
protected onPropertyPaneConfigurationStart(): void {
if (this.roleDefinitions) { return; }
this.getRoleDefinitions().then((e) => {
this.roleDefinitionsDropdownDisabled = false;
this.context.propertyPane.refresh();
});
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('rfxListTitle', {
label: strings.rfxListTitleLabel
}),
PropertyPaneTextField('rfxFoldersListTitle', {
label: strings.rfxFoldersListTitleLabel
}),
PropertyPaneTextField('archiveLibraryTitle', {
label: strings.archiveLibraryTitle
}),
PropertyPaneDropdown('roleDefinitionForLibraryMembersGroupOnSite', {
label: strings.roleDefinitionForLibraryMembersGroupOnSite,
options: this.roleDefinitions.map((rd) => {
return { key: rd.Name, text: rd.Name };
}),
}),
PropertyPaneDropdown('roleDefinitionForLibraryMembersGroupOnLibrary', {
label: strings.roleDefinitionForLibraryMembersGroupOnLibrary,
options: this.roleDefinitions.map((rd) => {
return { key: rd.Name, text: rd.Name };
}),
}),
PropertyPaneDropdown('roleDefinitionForLibraryMembersGroupOnFolder', {
label: strings.roleDefinitionForLibraryMembersGroupOnFolder,
options: this.roleDefinitions.map((rd) => {
return { key: rd.Name, text: rd.Name };
}),
}),
PropertyPaneDropdown('roleDefinitionForLibraryVisitorsGroupOnSite', {
label: strings.roleDefinitionForLibraryVisitorsGroupOnSite,
options: this.roleDefinitions.map((rd) => {
return { key: rd.Name, text: rd.Name };
}),
}),
PropertyPaneDropdown('roleDefinitionForLibraryVisitorsGroupOnLibrary', {
label: strings.roleDefinitionForLibraryVisitorsGroupOnLibrary,
options: this.roleDefinitions.map((rd) => {
return { key: rd.Name, text: rd.Name };
}),
}),
PropertyPaneDropdown('roleDefinitionForLibraryVisitorsGroupOnFolder', {
label: strings.roleDefinitionForLibraryVisitorsGroupOnFolder,
options: this.roleDefinitions.map((rd) => {
return { key: rd.Name, text: rd.Name };
}),
}),
PropertyPaneDropdown('roleDefinitionForFolderMembersOnSite', {
label: strings.roleDefinitionForFolderMembersOnSite,
options: this.roleDefinitions.map((rd) => {
return { key: rd.Name, text: rd.Name };
}),
}),
PropertyPaneDropdown('roleDefinitionForFolderMembersOnLibrary', {
label: strings.roleDefinitionForFolderMembersOnLibrary,
options: this.roleDefinitions.map((rd) => {
return { key: rd.Name, text: rd.Name };
}),
}),
PropertyPaneDropdown('roleDefinitionForFolderMembersOnFolder', {
label: strings.roleDefinitionForFolderMembersOnFolder,
options: this.roleDefinitions.map((rd) => {
return { key: rd.Name, text: rd.Name };
}),
}),
PropertyPaneDropdown('roleDefinitionForFolderVisitorsOnSite', {
label: strings.roleDefinitionForFolderVisitorsOnSite,
options: this.roleDefinitions.map((rd) => {
return { key: rd.Name, text: rd.Name };
}),
}),
PropertyPaneDropdown('roleDefinitionForFolderVisitorsOnLibrary', {
label: strings.roleDefinitionForFolderVisitorsOnLibrary,
options: this.roleDefinitions.map((rd) => {
return { key: rd.Name, text: rd.Name };
}),
}),
PropertyPaneDropdown('roleDefinitionForFolderVisitorsOnFolder', {
label: strings.roleDefinitionForFolderVisitorsOnFolder,
options: this.roleDefinitions.map((rd) => {
return { key: rd.Name, text: rd.Name };
}),
}),
PropertyPaneToggle('allowGroupNameChanges', {
label: strings.allowGroupNameChanges,
}),
PropertyPaneToggle('enablePrivateFolders', {
label:strings.privateFolders,
offText:strings.privateFoldersDisabled,
onText: strings.privateFoldersEnabled,
}),
PropertyPaneTextField('instrumentationKey', {
label: strings.instrumentationKey
}),
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,39 @@
import { ISiteGroupInfo } from "@pnp/sp/site-groups";
import { IRoleDefinition, IRoleDefinitionInfo } from '@pnp/sp/security';
import { ISiteUser } from "@pnp/sp/site-users";
import { SPHttpClient, SPHttpClientResponse, SPHttpClientConfiguration } from '@microsoft/sp-http';
export interface IRequestMaintenanceProps {
siteOwnersGroup:ISiteGroupInfo; // Site owners groupw will be given full contyrol on new libraros
rfxListTitle:string; // the name of the list that holds all the RFX Info
rfxFoldersListTitle:string; // the name of the list that holds all the RFX Folder Info
archiveLibraryTitle:string; // the name of the library that ther archive button moves stuff to.
webServerRelativeUrl:string;
roleDefinitions:Array<IRoleDefinitionInfo>;// all role definitions on the site
roleDefinitionForLibraryMembersGroupOnLibrary:string;// each new RFX library has an group created for internal users. What role should that group have on trghe library (Contribute)
roleDefinitionForLibraryMembersGroupOnSite: string; // each new RFX library has an group created for internal users. What role should that group have on trghe site (READ)
roleDefinitionForLibraryMembersGroupOnFolder: string; // each new RFX library has an group created for internal users. What role should that group have on trghe site (READ)
roleDefinitionForLibraryVisitorsGroupOnLibrary:string;// each new RFX library has an group created for external users. What role should that group have on trghe library (Contribute)
roleDefinitionForLibraryVisitorsGroupOnSite: string; // each new RFX library has an group created for external users. What role should that group have on trghe site (READ)
roleDefinitionForLibraryVisitorsGroupOnFolder: string; // each new RFX library has an group created for external users. What role should that group have on trghe site (READ)
roleDefinitionForFolderMembersOnFolder:string;
roleDefinitionForFolderMembersOnLibrary:string;
roleDefinitionForFolderMembersOnSite:string;
roleDefinitionForFolderVisitorsOnFolder:string;
roleDefinitionForFolderVisitorsOnLibrary:string;
roleDefinitionForFolderVisitorsOnSite:string;
allowGroupNameChanges:boolean;
enablePrivateFolders:boolean;
}

View File

@ -0,0 +1,37 @@
import { ISiteGroupInfo } from "@pnp/sp/site-groups";
import { IRoleDefinition, IRoleDefinitionInfo } from '@pnp/sp/security';
import { IRFx } from '../../../models/IRFx';
import { IRFxFolder } from '../../../models/IRFxFolder';
import { IActivity } from '../../../models/IActivity';
import { ISiteUser } from "@pnp/sp/site-users";
export interface IRequestMaintenanceState {
currentUserId: number;
currentUserEMail: string;
currentUserLoginName: string;
selectedRfx: IRFx;
showFolders: boolean;
rfxFolders:Array<IRFxFolder>;
rfxs: Array<IRFx>;
siteGroups: ISiteGroupInfo[];
showAddNewLibrary: boolean;
isUpdating: boolean;
invalidCharacters: boolean;
// used when adding an rfx::
newRfxId: string;
newRfxDescription: string;
newRFxClosingDate: Date;
newRFxLibraryMembersGroupName: string;
newRFxLibraryVisitorsGroupName: string;
newRFxLibraryOwnersGroupName: string;
showAddNewFolder: boolean;
newFolderName:string;
newFolderMembersGroupName:string;
newFolderVisitorsGroupName:string;
showActivity:boolean;
activities:Array<IActivity>;
mainSelectedItemsCount:number;
}

View File

@ -0,0 +1,67 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.requestMaintenance {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.commandBar button {
border-width: 0px;
}
.button {
// Our button
text-decoration: none;
height: 3200px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}

View File

@ -0,0 +1,32 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"instrumentationKey": "App Insights instrumentation key",
"privateFoldersEnabled": "Private folders enabled",
"privateFoldersDisabled": "Private folders enabled",
"privateFolders": "Private Folders",
"rfxListTitleLabel": "RFX List",
"rfxFoldersListTitleLabel": "RFX Folders List",
"roleDefinitionForLibraryMembersGroupOnLibrary": "Permission for library members on Libraries",
"roleDefinitionForLibraryMembersGroupOnSite": "Permission for library members on Site",
"roleDefinitionForLibraryMembersGroupOnFolder": "Permission for library members on Folders",
"roleDefinitionForLibraryVisitorsGroupOnLibrary": "Permission for library visitors on Libraries",
"roleDefinitionForLibraryVisitorsGroupOnSite": "Permission for library visitors on Site",
"roleDefinitionForLibraryVisitorsGroupOnFolder": "Permission for library visitors on Folders",
"roleDefinitionForFolderMembersOnFolder": "Permission for Folder members on Folders",
"roleDefinitionForFolderMembersOnLibrary": "Permission for Folder members on Library",
"roleDefinitionForFolderMembersOnSite": "Permission for Folder members on Site",
"roleDefinitionForFolderVisitorsOnFolder": "Permission for Folder visitors on Folders",
"roleDefinitionForFolderVisitorsOnLibrary": "Permission for Folder visitors on Library",
"roleDefinitionForFolderVisitorsOnSite": "Permission for Folder visitors on Site",
"allowGroupNameChanges": "Allow users to change generated security group names",
"archiveLibraryTitle": "Archive Library"
}
});

View File

@ -0,0 +1,37 @@
declare interface IRequestMaintenanceWebPartStrings {
instrumentationKey:string;
PropertyPaneDescription: string;
BasicGroupName: string;
rfxListTitleLabel:string;
rfxFoldersListTitleLabel:string;
archiveLibraryTitle:string;
privateFoldersEnabled:string;
privateFoldersDisabled:string;
privateFolders:string;
// Library member permissions
roleDefinitionForLibraryMembersGroupOnLibrary:string;
roleDefinitionForLibraryMembersGroupOnSite:string;
roleDefinitionForLibraryMembersGroupOnFolder:string;
// library visitor permissions
roleDefinitionForLibraryVisitorsGroupOnLibrary:string;
roleDefinitionForLibraryVisitorsGroupOnSite:string;
roleDefinitionForLibraryVisitorsGroupOnFolder:string;
// folder member permissions
roleDefinitionForFolderMembersOnFolder:string;
roleDefinitionForFolderMembersOnLibrary:string;
roleDefinitionForFolderMembersOnSite:string;
roleDefinitionForFolderVisitorsOnFolder:string;
roleDefinitionForFolderVisitorsOnLibrary:string;
roleDefinitionForFolderVisitorsOnSite:string;
allowGroupNameChanges:string;
}
declare module 'RequestMaintenanceWebPartStrings' {
const strings: IRequestMaintenanceWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "dca49132-f256-4f99-8509-864d663d4d3b",
"alias": "VendorMaintenanceWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportedHosts": ["SharePointWebPart"],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "VendorMaintenance" },
"description": { "default": "VendorMaintenance description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "VendorMaintenance"
}
}]
}

View File

@ -0,0 +1,152 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
IPropertyPaneConfiguration,
PropertyPaneTextField, PropertyPaneDropdown, IPropertyPaneDropdownOption
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import * as strings from 'VendorMaintenanceWebPartStrings';
import VendorMaintenance from './components/VendorMaintenance';
import { IVendorMaintenanceProps } from './components/IVendorMaintenanceProps';
import { ISiteGroupInfo } from '@pnp/sp/site-groups';
export interface IVendorMaintenanceWebPartProps {
ownersGroup: string;
roleDefinitionForSite: string;
}
import { setup as pnpSetup } from "@pnp/common";
import { sp } from "@pnp/sp";
import "@pnp/sp/site-groups/web";
import "@pnp/sp/webs";
import "@pnp/sp/site-groups/web";
import "@pnp/sp/security";
import { IRoleDefinitionInfo } from '@pnp/sp/security';
import { SPHttpClient, SPHttpClientResponse, SPHttpClientConfiguration } from '@microsoft/sp-http';
export default class VendorMaintenanceWebPart extends BaseClientSideWebPart<IVendorMaintenanceWebPartProps> {
private vistorsGroup: ISiteGroupInfo;
private ownersGroup: ISiteGroupInfo;
private membersGroup: ISiteGroupInfo;
private roleDefinitions: IRoleDefinitionInfo[];
private roleDefinitionsDropdownDisabled: boolean = true;
public onInit(): Promise<any> {
debugger;
//sessionStorage.setItem("spfx-debug", ""); //// REMOVE THIS
return super.onInit().then(_ => {
pnpSetup({
spfxContext: this.context
});
let groupsPromise = this.getGroups();
let roledefPromise = this.getRoleDefinitions();
return Promise.all([groupsPromise, roledefPromise]);
});
}
public getGroups(): Promise<any> {
let promises: Array<Promise<any>> = [];
promises.push(sp.web.associatedVisitorGroup()
.then((g) => {
this.vistorsGroup = g;
})
.catch((e) => {
debugger;
}));
// Gets the associated members group of a web
promises.push(sp.web.associatedMemberGroup()
.then((g) => {
this.membersGroup = g;
})
.catch((e) => {
debugger;
}));
// Gets the associated owners group of a web
promises.push(sp.web.associatedOwnerGroup()
.then((g) => {
this.ownersGroup = g;
})
.catch((e) => {
debugger;
}));
return Promise.all(promises);
}
public getRoleDefinitions(): Promise<void> {
return sp.site.rootWeb.roleDefinitions.get().then((rds) => {
this.roleDefinitions = rds;
});
}
public render(): void {
const element: React.ReactElement<IVendorMaintenanceProps> = React.createElement(
VendorMaintenance,
{
roleDefinitionForSite: this.properties.roleDefinitionForSite,
ownersGroup: this.ownersGroup,
webServerRelativeUrl: this.context.pageContext.web.serverRelativeUrl,
vendorListTitle: "Vendors",
roleDefinitions: this.roleDefinitions
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected onPropertyPaneConfigurationStart(): void {
debugger;
if (this.roleDefinitions) { return; }
this.getRoleDefinitions().then((e) => {
this.roleDefinitionsDropdownDisabled = false;
this.context.propertyPane.refresh();
});
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneDropdown('roleDefinitionForSite', {
label: strings.roleDefinitionForSiteLabel,
options: this.roleDefinitions.map((rd) => {
debugger;
return { key: rd.Name, text: rd.Name };
}),
}),
PropertyPaneTextField('ownersGroup', {
label: strings.ownersGroupLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,9 @@
import { ISiteGroupInfo } from "@pnp/sp/site-groups";
import { IRoleDefinition, IRoleDefinitionInfo } from '@pnp/sp/security';
export interface IVendorMaintenanceProps {
ownersGroup:ISiteGroupInfo;
vendorListTitle:string;
webServerRelativeUrl:string;
roleDefinitionForSite: string; //role to give new groups in the site
roleDefinitions:Array<IRoleDefinitionInfo>;
}

View File

@ -0,0 +1,10 @@
import { ISiteGroupInfo } from "@pnp/sp/site-groups";
import { IVendor } from '../../../models/IVendor';
export interface IVendorMaintenanceState {
vendors: Array<IVendor>;
newVendorTitle:string;
newVendorGroupName:string;
showAddNew:boolean;
isUpdating:boolean; //disable save button while update in progress
}

View File

@ -0,0 +1,74 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.vendorMaintenance {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}

View File

@ -0,0 +1,215 @@
import * as React from 'react';
import { sp, SPHttpClient } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/sites";
import "@pnp/sp/site-groups";
import "@pnp/sp/lists/web";
import "@pnp/sp/lists/web";
import "@pnp/sp/items";
import "@pnp/sp/security/list";
import "@pnp/sp/security/web";
import "@pnp/sp/folders";
import "@pnp/sp/views";
import { DetailsList } from 'office-ui-fabric-react/lib/DetailsList';
import { Label } from 'office-ui-fabric-react/lib/Label';
import { PrimaryButton, Button } from 'office-ui-fabric-react/lib/Button';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { Panel } from 'office-ui-fabric-react/lib/Panel';
import { Link } from 'office-ui-fabric-react/lib/Link';
import { ISiteGroupInfo } from '@pnp/sp/site-groups';
import { autobind } from 'office-ui-fabric-react/lib/Utilities';
import { IViewInfo } from '@pnp/sp/views';
import { IRoleDefinitionInfo } from '@pnp/sp/security';
import styles from './VendorMaintenance.module.scss';
import { IVendorMaintenanceProps } from './IVendorMaintenanceProps';
import { IVendorMaintenanceState } from './IVendorMaintenanceState';
import { escape } from '@microsoft/sp-lodash-subset';
import { IVendor } from '../../../models/IVendor';
import RFXUtilities from '../../../utilities/RFXUtilities';
export default class VendorMaintenance extends React.Component<IVendorMaintenanceProps, IVendorMaintenanceState> {
public state = {
vendors: [],
showAddNew: false,
newVendorTitle: null,
newVendorGroupName: null,
isUpdating: false,
invalidCharacters: false
};
public componentDidMount() {
this.fetchVendors();
}
/**
* Fetches the list of vendors (store in a sharepoint list) and updates them in the docLibs State varieble
*
* @private
* @memberof DocLibSecurity
*/
private fetchVendors() {
sp.web.lists.getByTitle(this.props.vendorListTitle).items.orderBy("Title").get().then((items) => {
this.setState((current) => ({
...current,
vendors: items.map((item) => {
return { title: item.Title, membersGroup: item["SecurityGroup"], id: item.Id };
})
})
);
});
}
/**
* Validates the data to add a new vendor and adds it if valid
*
* @private
* @returns
* @memberof DocLibSecurity
*/
private async validateAndAddVendor() {
debugger;
this.setState((current) => ({ ...current, isUpdating: true }));
//Do validation
let groupExists = await RFXUtilities.doesGroupExist(this.state.newVendorGroupName);
if (groupExists) {
alert("A group with this name already exists");
this.setState((current) => ({ ...current, isUpdating: false }));
return;
}
// Add the new group
await sp.web.siteGroups.add({ "Title": this.state.newVendorGroupName })
.catch((e) => {
alert("there was an error adding the group");
this.setState((current) => ({ ...current, isUpdating: false }));
return;
});
const newGroup = await sp.web.siteGroups.getByName(this.state.newVendorGroupName).get();
// set the groups owner
await RFXUtilities.setGroupOwner(newGroup.Id, this.props.ownersGroup.Id)
.catch((e) => {
alert("there was an setting the owner on the new group");
this.setState((current) => ({ ...current, isUpdating: false }));
return;
});
// await RFXUtilities.grantNewGroupAccessToSite(this.state.newVendorGroupName, this.props.roleDefinitionForSite,this.props.roleDefinitions).catch((e) => {
// alert("there was an error granting access to the site to the new group");
// this.setState((current) => ({ ...current, isUpdating: false }));
// return;
// });
debugger;
// add an entry to the Secured Libraries list to track this entry
await sp.web.lists.getByTitle(this.props.vendorListTitle).items.add({
Title: this.state.newVendorTitle,
SecurityGroup: this.state.newVendorGroupName
})
.catch((e) => {
alert("there was an updating the List of Libraries");
this.setState((current) => ({ ...current, isUpdating: false }));
return;
});
this.fetchVendors();
this.setState((current) => ({ ...current, showAddNew: false, isUpdating: false, newVendorGroupName: "", newVendorTitle: "" }));
}
private async deleteVendor(item: IVendor) {
this.setState((current) => ({ ...current, isUpdating: true }));
//Do validation
let groupExists = await RFXUtilities.doesGroupExist(item.membersGroup);
if (!groupExists) {
alert("A group with this name does not exist ");
this.setState((current) => ({ ...current, isUpdating: false }));
return;
}
await RFXUtilities.deletegroup(item.membersGroup)
.catch((e) => {
alert("there was an error deleting the group");
this.setState((current) => ({ ...current, isUpdating: false }));
return;
});
// await RFXUtilities.deleterow(item.id,this.props.vendorListTitle)
// .catch((e) => {
// alert("there was an removing the row from the vendors list");
// this.setState((current) => ({ ...current, isUpdating: false }));
// return;
// });
this.fetchVendors();
this.setState((current) => ({ ...current, showAddNew: false, isUpdating: false, newVendorGroupName: "", newVendorTitle: "" }));
}
public render(): React.ReactElement<IVendorMaintenanceProps> {
return (
<div className={styles.vendorMaintenance}>
<Panel isOpen={this.state.showAddNew} >
<TextField label="Vendor Name" placeholder="enter vendor name" value={this.state.newVendorTitle} onChange={(e, value) => {
this.setState((current) => ({
...current,
newVendorTitle: value.replace(/[&\/\\#,+()$~% "`:;*?<>{}|^!@$']/g, ''),
newVendorGroupName: value.replace(/[&\/\\#,+()$~% "`:;*?<>{}|^!@$']/g, '') + " Members"
}));
}}></TextField>
<TextField label="Group Name" placeholder="enter group name" value={this.state.newVendorGroupName}
default={this.state.newVendorGroupName} onChange={(e, value) => {
this.setState((current) => ({ ...current, newVendorGroupName: value.replace(/[&\/\\#,+()$~% "`:;*?<>{}|^!@$']/g, '') }));
}}></TextField>
<PrimaryButton disabled={this.state.isUpdating || this.state.invalidCharacters} onClick={(e) => {
this.validateAndAddVendor();
}}>Add Vendor</PrimaryButton>
</Panel>
<DetailsList
items={this.state.vendors}
columns={[
{
key: 'column1', name: 'Vendor Name', fieldName: 'title', minWidth: 100, maxWidth: 200, isResizable: true,
onRender: (item: IVendor) => (
<Label>{item.title}</Label>
)
},
{
key: 'column3', name: 'Security Group', fieldName: 'membersGroup', minWidth: 100, maxWidth: 200, isResizable: true,
onRender: (item: IVendor) => (
<Link onClick={() => {
RFXUtilities.linkToGroup(item.membersGroup, this.props.webServerRelativeUrl);
}}>{item.membersGroup}</Link>
)
},
{
key: 'edit', name: 'Edit', fieldName: 'edit', minWidth: 100, maxWidth: 200, isResizable: false,
onRender: (item: IVendor) => (
<Link onClick={() => {
this.deleteVendor(item);
}}>Delete</Link>
)
}
]}
>
</DetailsList>
<Button onClick={(e) => this.setState((current) => ({ ...current, showAddNew: true, newVendorGroupName: "", newVendorTitle: "" }))}>Add new Vendor</Button>
</div>
);
}
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"roleDefinitionForSiteLabel": "Role def users get on site",
"ownersGroupLabel": "group that owns the groups"
}
});

View File

@ -0,0 +1,12 @@
declare interface IVendorMaintenanceWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
roleDefinitionForSiteLabel: string;
ownersGroupLabel: string;
}
declare module 'VendorMaintenanceWebPartStrings' {
const strings: IVendorMaintenanceWebPartStrings;
export = strings;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

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,38 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"es6-promise",
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection"
]
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"lib"
]
}

View File

@ -0,0 +1,30 @@
{
"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
}
}