Merge pull request #1724 from fthorild/sites-selected
This commit is contained in:
commit
011f186186
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"@microsoft/generator-sharepoint": {
|
||||||
|
"isCreatingSolution": true,
|
||||||
|
"environment": "spo",
|
||||||
|
"version": "1.11.0",
|
||||||
|
"libraryName": "site-selected-mngr-wp",
|
||||||
|
"libraryId": "7fec6393-3d66-4b11-8d55-5f609edf2a7a",
|
||||||
|
"packageManager": "npm",
|
||||||
|
"isDomainIsolated": true,
|
||||||
|
"componentType": "webpart"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
# Sites Selected Admin client-side web part
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This is a sample SharePoint Framework client-side web part built using react.
|
||||||
|
|
||||||
|
The web part lets you manage your Azure AD applications that have the Sites Selected Api permission. With this web part you'll get an UI for managing what app can connect to which site.
|
||||||
|
|
||||||
|
The web part uses the built in MSGraphClient and needs to be approved in API management. The app asks for Microsoft Graph `Application.Read.All` and `Sites.FullControl.All`. Users of the web part will need to have Site Collection Administrator privileges to the sites being added to an app.
|
||||||
|
|
||||||
|
## Web part usage
|
||||||
|
|
||||||
|
![alt text][Webpart in action]
|
||||||
|
|
||||||
|
[Webpart in action]: ./assets/sites-manager-demo.gif "Sites Selected Manager in action"
|
||||||
|
|
||||||
|
## Using the web part to grant an app access to a site, start to finish
|
||||||
|
|
||||||
|
![alt text][Webpart in action - Visual Studio]
|
||||||
|
|
||||||
|
[Webpart in action - Visual Studio]: ./assets/vsDemo.gif "Sites Selected Manager Demo"
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
![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)
|
||||||
|
![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 Hosted: Does not work with local workbench](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Does not work with local workbench")
|
||||||
|
|
||||||
|
## Applies to
|
||||||
|
|
||||||
|
- [SharePoint Framework](https://aka.ms/spfx)
|
||||||
|
- [Microsoft 365 tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
|
||||||
|
|
||||||
|
> Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram)
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
> One (or more) Azure AD app with Sites.Selected and the possibility to approve requests in API management (SharePoint Administrator). Site collection administrator is needed for the site(s) you want to give app access to.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Solution|Author(s)
|
||||||
|
--------|---------
|
||||||
|
react-sites-selected-admin | Fredrik Thorild [@fthorild](https://twitter.com/fthorild)
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
Version|Date|Comments
|
||||||
|
-------|----|--------
|
||||||
|
1.0|February 19, 2021|Initial release
|
||||||
|
|
||||||
|
## 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.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Minimal Path to Awesome
|
||||||
|
|
||||||
|
- Add an app in Azure AD, or for an existing app add the Sites.Selected Microsoft Graph api permission
|
||||||
|
|
||||||
|
![alt text](./assets/aad-appreg.png "AAD app reg")
|
||||||
|
|
||||||
|
- Clone this repository
|
||||||
|
- Ensure that you are at the solution folder
|
||||||
|
- in the command-line run:
|
||||||
|
- **gulp bundle --ship**
|
||||||
|
- **gulp package-solution --ship**
|
||||||
|
- Add the `.sppkg` package to your app catalog
|
||||||
|
- Approve the api access requests
|
||||||
|
|
||||||
|
![alt text](./assets/api-access-page.png "API Management")
|
||||||
|
|
||||||
|
- Install web part on a site of your choice
|
||||||
|
- Add permissions to your app
|
||||||
|
- Try out the AAD app by sending a request using your favorite method
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Getting started with SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
|
||||||
|
- [Building for Microsoft teams](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-overview)
|
||||||
|
- [Use Microsoft Graph in your solution](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis)
|
||||||
|
- [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview)
|
||||||
|
- [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-web parts/samples/react-content-query-online" />
|
Binary file not shown.
After Width: | Height: | Size: 74 KiB |
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
Binary file not shown.
After Width: | Height: | Size: 30 MiB |
Binary file not shown.
After Width: | Height: | Size: 10 MiB |
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||||
|
"version": "2.0",
|
||||||
|
"bundles": {
|
||||||
|
"sites-selected-manager-web-part": {
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"entrypoint": "./lib/webparts/sitesSelectedManager/SitesSelectedManagerWebPart.js",
|
||||||
|
"manifest": "./src/webparts/sitesSelectedManager/SitesSelectedManagerWebPart.manifest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externals": {},
|
||||||
|
"localizedResources": {
|
||||||
|
"SitesSelectedManagerWebPartStrings": "lib/webparts/sitesSelectedManager/loc/{locale}.js"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||||
|
"deployCdnPath": "temp/deploy"
|
||||||
|
}
|
|
@ -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": "site-selected-mngr-wp",
|
||||||
|
"accessKey": "<!-- ACCESS KEY -->"
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||||
|
"solution": {
|
||||||
|
"name": "site-selected-mngr-wp-client-side-solution",
|
||||||
|
"id": "7fec6393-3d66-4b11-8d55-5f609edf2a7a",
|
||||||
|
"version": "1.0.0.0",
|
||||||
|
"includeClientSideAssets": true,
|
||||||
|
"isDomainIsolated": true,
|
||||||
|
"developer": {
|
||||||
|
"name": "Fredrik Thorild",
|
||||||
|
"websiteUrl": "https://twitter.com/taxonomythorild",
|
||||||
|
"privacyUrl": "",
|
||||||
|
"termsOfUseUrl": "",
|
||||||
|
"mpnId": ""
|
||||||
|
},
|
||||||
|
"webApiPermissionRequests": [
|
||||||
|
{
|
||||||
|
"resource": "Microsoft Graph",
|
||||||
|
"scope": "Application.Read.All"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": "Microsoft Graph",
|
||||||
|
"scope": "Sites.FullControl.All"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"zippedPackage": "solution/site-selected-mngr-wp.sppkg"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||||
|
"port": 4321,
|
||||||
|
"https": true,
|
||||||
|
"initialPage": "https://localhost:5432/workbench",
|
||||||
|
"api": {
|
||||||
|
"port": 5432,
|
||||||
|
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||||
|
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||||
|
}
|
|
@ -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
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"name": "site-selected-mngr-wp",
|
||||||
|
"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": {
|
||||||
|
"@fluentui/react": "^7.160.1",
|
||||||
|
"@microsoft/sp-core-library": "1.11.0",
|
||||||
|
"@microsoft/sp-lodash-subset": "1.11.0",
|
||||||
|
"@microsoft/sp-office-ui-fabric-core": "1.11.0",
|
||||||
|
"@microsoft/sp-property-pane": "1.11.0",
|
||||||
|
"@microsoft/sp-webpart-base": "1.11.0",
|
||||||
|
"office-ui-fabric-react": "6.214.0",
|
||||||
|
"react": "16.8.5",
|
||||||
|
"react-dom": "16.8.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "16.8.8",
|
||||||
|
"@types/react-dom": "16.8.3",
|
||||||
|
"@microsoft/sp-build-web": "1.11.0",
|
||||||
|
"@microsoft/sp-tslint-rules": "1.11.0",
|
||||||
|
"@microsoft/sp-module-interfaces": "1.11.0",
|
||||||
|
"@microsoft/sp-webpart-workbench": "1.11.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",
|
||||||
|
"@types/webpack-env": "1.13.1",
|
||||||
|
"@types/es6-promise": "0.0.33"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||||
|
"id": "a4411651-9a37-4956-8948-ec5b053da96e",
|
||||||
|
"alias": "SitesSelectedManagerWebPart",
|
||||||
|
"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": "Sites Selected Manager"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"default": "Webpart for managing Azure AD Apps granting site specific access"
|
||||||
|
},
|
||||||
|
"officeFabricIconFontName": "AzureLogo",
|
||||||
|
"properties": {
|
||||||
|
"description": "Sites Selected Manager",
|
||||||
|
"showAbout": true,
|
||||||
|
"aadGuid":"883ea226-0bf2-4a8f-9f9d-92c9162a727d"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactDom from 'react-dom';
|
||||||
|
import { Version } from '@microsoft/sp-core-library';
|
||||||
|
import {
|
||||||
|
IPropertyPaneConfiguration,
|
||||||
|
PropertyPaneTextField,
|
||||||
|
PropertyPaneToggle
|
||||||
|
} from '@microsoft/sp-property-pane';
|
||||||
|
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||||
|
|
||||||
|
import * as strings from 'SitesSelectedManagerWebPartStrings';
|
||||||
|
import SitesSelectedManager from './components/SitesSelectedManager';
|
||||||
|
import { ISitesSelectedManagerProps } from './components/ISitesSelectedManagerProps';
|
||||||
|
|
||||||
|
export interface ISitesSelectedManagerWebPartProps {
|
||||||
|
description: string;
|
||||||
|
showAbout: boolean;
|
||||||
|
aadGuid: string;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SitesSelectedManagerWebPart extends BaseClientSideWebPart<ISitesSelectedManagerWebPartProps> {
|
||||||
|
|
||||||
|
public render(): void {
|
||||||
|
const element: React.ReactElement<ISitesSelectedManagerProps> = React.createElement(
|
||||||
|
SitesSelectedManager,
|
||||||
|
{
|
||||||
|
description: this.properties.description,
|
||||||
|
context: this.context,
|
||||||
|
showAbout: this.properties.showAbout,
|
||||||
|
aadGuid: this.properties.aadGuid
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ReactDom.render(element, this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onDispose(): void {
|
||||||
|
ReactDom.unmountComponentAtNode(this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get dataVersion(): Version {
|
||||||
|
return Version.parse('1.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
|
return {
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
groupFields: [
|
||||||
|
PropertyPaneTextField('description', {
|
||||||
|
label: strings.DescriptionFieldLabel
|
||||||
|
}),
|
||||||
|
PropertyPaneToggle('showAbout',
|
||||||
|
{
|
||||||
|
label: strings.ShowAboutFieldLabel,
|
||||||
|
checked: this.properties.showAbout === true,
|
||||||
|
}),
|
||||||
|
PropertyPaneTextField('aadGuid', {
|
||||||
|
label: strings.AADGuidLabel
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { ICommandBarItemProps } from "office-ui-fabric-react";
|
||||||
|
import { ISitesSelectedManagerProps } from "./ISitesSelectedManagerProps";
|
||||||
|
|
||||||
|
|
||||||
|
export interface IDialogProps {
|
||||||
|
isHidden: boolean;
|
||||||
|
hideDialog: (hide: boolean) => void;
|
||||||
|
webPartProperties: ISitesSelectedManagerProps,
|
||||||
|
selectedApp: string;
|
||||||
|
isDeleteMode: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAppListItem {
|
||||||
|
key: number;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAppListState {
|
||||||
|
items?: IAppListItem[];
|
||||||
|
selectionDetails?: string;
|
||||||
|
menuItems?: ICommandBarItemProps[];
|
||||||
|
dialogHidden?: boolean,
|
||||||
|
isDeleteMode?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMessageBoxProps {
|
||||||
|
resetChoice?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISitePermissionList {
|
||||||
|
value: ISitesSelectedPermissionPayload[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISitesSelectedPermissionPayload {
|
||||||
|
roles?: string[];
|
||||||
|
grantedToIdentities?: IAADApplicationWrapper[];
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISelectedSitesListProps {
|
||||||
|
value: Array<IAADApplication>;
|
||||||
|
webpartProperties: ISitesSelectedManagerProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISPSite {
|
||||||
|
displayName: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAADApplicationList {
|
||||||
|
value: Array<IAADApplication>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAADApplication {
|
||||||
|
id: string;
|
||||||
|
appId?: string;
|
||||||
|
displayName: string;
|
||||||
|
requiredResourceAccess?: Array<IRequiredResourceAccess>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAADApplicationWrapper {
|
||||||
|
application: IAADApplication;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRequiredResourceAccess {
|
||||||
|
resourceAppId: string;
|
||||||
|
resourceAccess: Array<IResourceAccess>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IResourceAccess {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { WebPartContext } from '@microsoft/sp-webpart-base';
|
||||||
|
|
||||||
|
export interface ISitesSelectedManagerProps {
|
||||||
|
description: string;
|
||||||
|
context: WebPartContext;
|
||||||
|
showAbout: boolean;
|
||||||
|
aadGuid: string;
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { IAADApplication, IAADApplicationList, IMessageBoxProps, ISPSite } from './ISitesSelectedAppInterfaces';
|
||||||
|
import { ISitesSelectedManagerProps } from './ISitesSelectedManagerProps';
|
||||||
|
import { SitesSelectedAppList } from './SitesSelectedList';
|
||||||
|
import { Icon, MessageBar, MessageBarType, Pivot, PivotItem, PrimaryButton, Stack, TextField } from 'office-ui-fabric-react';
|
||||||
|
import { Spinner } from '@fluentui/react';
|
||||||
|
import styles from './SitesSelectedManager.module.scss';
|
||||||
|
|
||||||
|
|
||||||
|
export const SitesSelectedApp: React.FunctionComponent<ISitesSelectedManagerProps> = (props) => {
|
||||||
|
const [appState, setAppState] = React.useState<Array<IAADApplication>>()
|
||||||
|
const [site, setSite] = React.useState("");
|
||||||
|
const [permString, setpermString] = React.useState("");
|
||||||
|
const [showMessage, setShowMessage] = React.useState(false);
|
||||||
|
const [messageBarType, setMessageBarType] = React.useState<MessageBarType>();
|
||||||
|
const [message, setMessage] = React.useState("");
|
||||||
|
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
setShowMessage(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = await props.context.msGraphClientFactory.getClient();
|
||||||
|
const aadApps: IAADApplicationList = await client
|
||||||
|
.api('applications')
|
||||||
|
.version("v1.0")
|
||||||
|
.select("id,appId,displayName,requiredResourceAccess")
|
||||||
|
.get();
|
||||||
|
const appsWithSitesSelected = aadApps.value.filter((obj) => {
|
||||||
|
return obj.requiredResourceAccess.some(({ resourceAccess }) =>
|
||||||
|
resourceAccess.some(({ id }) => id === props.aadGuid))
|
||||||
|
});
|
||||||
|
setAppState(appsWithSitesSelected);
|
||||||
|
|
||||||
|
if (appsWithSitesSelected.length === 0) {
|
||||||
|
setMessageBarType(MessageBarType.info)
|
||||||
|
setMessage(`We couldn't find any apps with [Sites.Selected] - Don't think that's right?
|
||||||
|
Then you might want to double check the guid in webpart settings - The default is 883ea226-0bf2-4a8f-9f9d-92c9162a727d`);
|
||||||
|
setShowMessage(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
setMessageBarType(MessageBarType.error)
|
||||||
|
if (error.statusCode) {
|
||||||
|
setMessage(`Http error occured ${error.statusCode} - ${error.message} - have you consented this web part in API management?`);
|
||||||
|
} else {
|
||||||
|
setMessage(`Unknown error occured getting your apps - have you consented this web part in API management?`);
|
||||||
|
}
|
||||||
|
setAppState([]);
|
||||||
|
setShowMessage(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchData()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
const checkSitePermission = async () => {
|
||||||
|
setpermString("...loading - Getting site");
|
||||||
|
setShowMessage(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = new URL(site);
|
||||||
|
const client = await props.context.msGraphClientFactory.getClient();
|
||||||
|
const siteData: ISPSite = await client
|
||||||
|
.api(`sites/${url.hostname}:${url.pathname}`)
|
||||||
|
.version("v1.0")
|
||||||
|
.select("displayName,id,description")
|
||||||
|
.get();
|
||||||
|
setpermString("...loading - Got the site");
|
||||||
|
|
||||||
|
const perms = await client
|
||||||
|
.api(`sites/${siteData.id}/permissions`)
|
||||||
|
.version("v1.0")
|
||||||
|
.get()
|
||||||
|
|
||||||
|
setpermString(JSON.stringify(perms.value, undefined, 4))
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
setMessageBarType(MessageBarType.error)
|
||||||
|
if (error.statusCode) {
|
||||||
|
setMessage(`Http error occured ${error.statusCode} - ${error.message} - Check the format of your URL
|
||||||
|
Correct format below:
|
||||||
|
https://tenant.sharepoint.com/sites/thesite`);
|
||||||
|
} else {
|
||||||
|
setMessage(`Unknown error`);
|
||||||
|
}
|
||||||
|
setpermString("");
|
||||||
|
setShowMessage(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const SitesSelectedMessageBox = (p: IMessageBoxProps) => (
|
||||||
|
<MessageBar
|
||||||
|
messageBarType={messageBarType}
|
||||||
|
isMultiline={true}
|
||||||
|
onDismiss={p.resetChoice}
|
||||||
|
dismissButtonAriaLabel="Close"
|
||||||
|
>
|
||||||
|
{message}
|
||||||
|
|
||||||
|
</MessageBar>
|
||||||
|
);
|
||||||
|
const resetChoice = React.useCallback(() => setShowMessage(false), []);
|
||||||
|
|
||||||
|
if (appState) {
|
||||||
|
return <div>
|
||||||
|
<h1>{props.description}</h1>
|
||||||
|
<Pivot>
|
||||||
|
{props.showAbout ? <PivotItem
|
||||||
|
headerText="Home / About"
|
||||||
|
headerButtonProps={{
|
||||||
|
'data-order': 1,
|
||||||
|
'data-title': 'Home / About'
|
||||||
|
}}
|
||||||
|
itemIcon="Home"
|
||||||
|
>
|
||||||
|
<h3>What can this webpart do?</h3>
|
||||||
|
<ul>
|
||||||
|
<li><Icon iconName="SharepointAppIcon16" /> List Azure AD applications that have the Microsoft graph api scope [Sites.Selected]</li>
|
||||||
|
<li><Icon iconName="SharepointAppIcon16" /> Add SharePoint sites to the listed apps which will enable the app to interact with these sites via the graph api</li>
|
||||||
|
<li><Icon iconName="SharepointAppIcon16" /> Clear all SharePoint site permissions for the selected app</li>
|
||||||
|
|
||||||
|
<li><Icon iconName="Permissions" /> Check what app(s) that has been added to a specific SharePoint site</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
<h3>Good to know</h3>
|
||||||
|
<p>
|
||||||
|
Due to api- and other limitations it is "not possible" to list all sites that have an app with permissions via this concept.
|
||||||
|
Furthermore, when checking a site you will see that it has n apps with access but not what access (Read,Write or Read/Write)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<h3>User access</h3>
|
||||||
|
<p>
|
||||||
|
In order to grant access for an app, the user of this webpart has to be a Site Collection Administrator of the site.
|
||||||
|
</p>
|
||||||
|
</PivotItem> : React.Fragment}
|
||||||
|
<PivotItem headerText="Add/Remove sites to Apps" itemIcon="SharepointAppIcon16">
|
||||||
|
<SitesSelectedAppList {...{ value: appState, webpartProperties: props }} />
|
||||||
|
{showMessage ? <SitesSelectedMessageBox resetChoice={resetChoice} /> : React.Fragment}
|
||||||
|
</PivotItem>
|
||||||
|
<PivotItem headerText="Check app permissions on a site" itemIcon="Permissions">
|
||||||
|
|
||||||
|
<h3>Use the form below to check a sites permissions</h3>
|
||||||
|
<p><strong>Info!</strong> If the result box shows [] it means there is no permissions granted</p>
|
||||||
|
|
||||||
|
<Stack className={styles.checkPermUi}>
|
||||||
|
|
||||||
|
<TextField onChange={(e: any) => setSite(e.target.value)} label="SharePoint site"
|
||||||
|
placeholder="Please enter URL here" />
|
||||||
|
|
||||||
|
<PrimaryButton text="Check permission" onClick={checkSitePermission} allowDisabledFocus />
|
||||||
|
|
||||||
|
<TextField value={permString} label="(Raw) - Permission object for site" multiline autoAdjustHeight />
|
||||||
|
|
||||||
|
</Stack>
|
||||||
|
{showMessage ? <SitesSelectedMessageBox resetChoice={resetChoice} /> : React.Fragment}
|
||||||
|
|
||||||
|
</PivotItem>
|
||||||
|
</Pivot>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return <div>
|
||||||
|
<Spinner label="Working on it..." />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,213 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
|
||||||
|
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
|
||||||
|
import { ChoiceGroup, IChoiceGroupOption } from 'office-ui-fabric-react/lib/ChoiceGroup';
|
||||||
|
import { MessageBar, MessageBarType, TextField } from 'office-ui-fabric-react';
|
||||||
|
import { ISitesSelectedManagerProps } from './ISitesSelectedManagerProps';
|
||||||
|
import styles from './SitesSelectedManager.module.scss';
|
||||||
|
import { IAADApplicationWrapper, IDialogProps, IMessageBoxProps, ISitePermissionList, ISitesSelectedPermissionPayload, ISPSite } from './ISitesSelectedAppInterfaces';
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
key: 'read',
|
||||||
|
text: 'Read',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'write',
|
||||||
|
text: 'Write',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'read-write',
|
||||||
|
text: 'Read / Write',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const modelProps = {
|
||||||
|
isBlocking: false,
|
||||||
|
styles: { main: { maxWidth: 450 } },
|
||||||
|
};
|
||||||
|
const addDialogContentProps = {
|
||||||
|
type: DialogType.largeHeader,
|
||||||
|
title: 'Grant access to the selected app to a SharePoint site collection',
|
||||||
|
subText: 'Enter a SharePoint site collection URL into the text field and select the wanted access level',
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteDialogContentProps = {
|
||||||
|
type: DialogType.largeHeader,
|
||||||
|
title: 'Remove the access for the selected app to a SharePoint site collection',
|
||||||
|
subText: 'Enter a SharePoint site collection URL into the text field and click "remove" to remove the access',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const SitesSelectedDialog: React.FunctionComponent<IDialogProps> = (props) => {
|
||||||
|
const [site, setSite] = React.useState("");
|
||||||
|
const [perm, setPerm] = React.useState("");
|
||||||
|
const [showMessage, setShowMessage] = React.useState(false);
|
||||||
|
const [messageBarType, setMessageBarType] = React.useState<MessageBarType>();
|
||||||
|
const [message, setMessage] = React.useState("");
|
||||||
|
|
||||||
|
const _addPermissionToSite = async () => {
|
||||||
|
setShowMessage(false);
|
||||||
|
try {
|
||||||
|
const url = new URL(site);
|
||||||
|
const client = await props.webPartProperties.context.msGraphClientFactory.getClient();
|
||||||
|
const siteData: ISPSite = await client
|
||||||
|
.api(`sites/${url.hostname}:${url.pathname}`)
|
||||||
|
.version("v1.0")
|
||||||
|
.select("displayName,id,description")
|
||||||
|
.get()
|
||||||
|
|
||||||
|
const app: IAADApplicationWrapper = { application: { displayName: props.selectedApp.split('|')[0], id: props.selectedApp.split('|')[1] } }
|
||||||
|
const pl: ISitesSelectedPermissionPayload = {
|
||||||
|
roles: perm.split('-'),
|
||||||
|
grantedToIdentities: [app]
|
||||||
|
}
|
||||||
|
|
||||||
|
await client
|
||||||
|
.api(`sites/${siteData.id}/permissions`)
|
||||||
|
.version("v1.0")
|
||||||
|
.post(pl)
|
||||||
|
|
||||||
|
_handleSuccess("Yay! - Permissions successfully added!");
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
_handleError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const _deletePermissionToSite = async () => {
|
||||||
|
|
||||||
|
try {
|
||||||
|
setShowMessage(false);
|
||||||
|
const url = new URL(site);
|
||||||
|
const client = await props.webPartProperties.context.msGraphClientFactory.getClient();
|
||||||
|
const siteData: ISPSite = await client
|
||||||
|
.api(`sites/${url.hostname}:${url.pathname}`)
|
||||||
|
.version("v1.0")
|
||||||
|
.select("displayName,id,description")
|
||||||
|
.get()
|
||||||
|
const permList: ISitePermissionList = await client
|
||||||
|
.api(`sites/${siteData.id}/permissions`)
|
||||||
|
.version("v1.0")
|
||||||
|
.get()
|
||||||
|
const permissionIdToRemove = _getPermissionIdFromPayload(props.selectedApp.split('|')[1], permList);
|
||||||
|
|
||||||
|
if (permissionIdToRemove) {
|
||||||
|
await client
|
||||||
|
.api(`sites/${siteData.id}/permissions/${permissionIdToRemove}`)
|
||||||
|
.version("v1.0")
|
||||||
|
.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleSuccess("Yay! - Permissions successfully deleted!");
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
_handleError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _handleError = (error: any) => {
|
||||||
|
setMessageBarType(MessageBarType.error)
|
||||||
|
if (error.statusCode) {
|
||||||
|
setMessage(`Http error occured ${error.statusCode} - ${error.message} - Check the format of your URL
|
||||||
|
Correct format below:
|
||||||
|
https://tenant.sharepoint.com/sites/thesite`);
|
||||||
|
} else {
|
||||||
|
setMessage(`Unknown error occured`);
|
||||||
|
}
|
||||||
|
setShowMessage(true);
|
||||||
|
props.hideDialog(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _handleSuccess = (message: string) => {
|
||||||
|
setMessageBarType(MessageBarType.success)
|
||||||
|
setMessage(message);
|
||||||
|
setShowMessage(true);
|
||||||
|
props.hideDialog(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setShowMessage(false);
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _getPermissionIdFromPayload = (appId: string, payload: ISitePermissionList): string => {
|
||||||
|
let result: string;
|
||||||
|
payload.value.forEach(element => {
|
||||||
|
element.grantedToIdentities.forEach(el => {
|
||||||
|
console.warn(el.application.id === appId);
|
||||||
|
if (el.application.id === appId)
|
||||||
|
result = element.id
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!result) {
|
||||||
|
throw new Error("App could not be found for site");
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _onChange = (ev: React.FormEvent<HTMLInputElement>, option: IChoiceGroupOption): void => {
|
||||||
|
setPerm(option.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
const SitesSelectedStatusMessage = (p: IMessageBoxProps) => (
|
||||||
|
<MessageBar
|
||||||
|
messageBarType={messageBarType}
|
||||||
|
isMultiline={true}
|
||||||
|
onDismiss={p.resetChoice}
|
||||||
|
dismissButtonAriaLabel="Close"
|
||||||
|
>
|
||||||
|
{message}
|
||||||
|
|
||||||
|
</MessageBar>
|
||||||
|
);
|
||||||
|
|
||||||
|
const resetChoice = React.useCallback(() => setShowMessage(false), []);
|
||||||
|
if (props.isDeleteMode) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
|
||||||
|
{showMessage ? <SitesSelectedStatusMessage resetChoice={resetChoice} /> : React.Fragment}
|
||||||
|
|
||||||
|
<Dialog className={styles.sitesSelectedManager}
|
||||||
|
hidden={props.isHidden}
|
||||||
|
onDismiss={(() => { props.hideDialog(true) })}
|
||||||
|
dialogContentProps={deleteDialogContentProps}
|
||||||
|
modalProps={modelProps}
|
||||||
|
|
||||||
|
>
|
||||||
|
|
||||||
|
<TextField onChange={(e: any) => setSite(e.target.value)} label="SharePoint site"
|
||||||
|
placeholder="Please enter URL here" />
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<PrimaryButton onClick={_deletePermissionToSite} text="Save" />
|
||||||
|
<DefaultButton onClick={(() => props.hideDialog(true))} text="Cancel" />
|
||||||
|
</DialogFooter>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
|
||||||
|
{showMessage ? <SitesSelectedStatusMessage resetChoice={resetChoice} /> : React.Fragment}
|
||||||
|
|
||||||
|
<Dialog className={styles.sitesSelectedManager}
|
||||||
|
hidden={props.isHidden}
|
||||||
|
onDismiss={(() => { props.hideDialog(true) })}
|
||||||
|
dialogContentProps={addDialogContentProps}
|
||||||
|
modalProps={modelProps}
|
||||||
|
>
|
||||||
|
|
||||||
|
<TextField onChange={(e: any) => setSite(e.target.value)} label="SharePoint site"
|
||||||
|
placeholder="Please enter URL here" />
|
||||||
|
|
||||||
|
<ChoiceGroup onChange={_onChange} options={options} />
|
||||||
|
<DialogFooter>
|
||||||
|
<PrimaryButton onClick={_addPermissionToSite} text="Save" />
|
||||||
|
<DefaultButton onClick={(() => props.hideDialog(true))} text="Cancel" />
|
||||||
|
</DialogFooter>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,142 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { DetailsList, DetailsListLayoutMode, Selection, IColumn, CheckboxVisibility, SelectionMode } from 'office-ui-fabric-react/lib/DetailsList';
|
||||||
|
import { MarqueeSelection } from 'office-ui-fabric-react/lib/MarqueeSelection';
|
||||||
|
import { Fabric } from 'office-ui-fabric-react/lib/Fabric';
|
||||||
|
import { IAppListItem, IAppListState, ISelectedSitesListProps } from './ISitesSelectedAppInterfaces';
|
||||||
|
import { ICommandBarItemProps, CommandBar, IButtonProps } from 'office-ui-fabric-react';
|
||||||
|
import styles from './SitesSelectedManager.module.scss';
|
||||||
|
import { SitesSelectedDialog } from './SitesSelectedDialog';
|
||||||
|
|
||||||
|
export class SitesSelectedAppList extends React.Component<ISelectedSitesListProps, IAppListState> {
|
||||||
|
private _selection: Selection;
|
||||||
|
private _allItems: IAppListItem[];
|
||||||
|
private _columns: IColumn[];
|
||||||
|
private _items: ICommandBarItemProps[];
|
||||||
|
private _overflowButtonProps: IButtonProps;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this._hideDialog = this._hideDialog.bind(this);
|
||||||
|
this._items = this._getMenu(true);
|
||||||
|
this._selection = new Selection({
|
||||||
|
onSelectionChanged: () => this.setState({ selectionDetails: this._getSelectionDetails() }),
|
||||||
|
});
|
||||||
|
|
||||||
|
this._allItems = [];
|
||||||
|
let i = 0;
|
||||||
|
this.props.value.forEach(element => {
|
||||||
|
this._allItems.push({
|
||||||
|
key: i,
|
||||||
|
name: element.displayName,
|
||||||
|
value: element.appId,
|
||||||
|
});
|
||||||
|
i = i + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
this._columns = [
|
||||||
|
{ key: 'column1', name: 'App Name', fieldName: 'name', minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
{ key: 'column2', name: 'Azure AD App Id', fieldName: 'value', minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
items: this._allItems,
|
||||||
|
selectionDetails: this._getSelectionDetails(),
|
||||||
|
menuItems: this._items,
|
||||||
|
dialogHidden: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _hideDialog(hide: boolean) {
|
||||||
|
this.setState(
|
||||||
|
{ dialogHidden: hide }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
private _getSelectionDetails(): string {
|
||||||
|
const selectionCount = this._selection.getSelectedCount();
|
||||||
|
switch (selectionCount) {
|
||||||
|
case 0:
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
menuItems: this._getMenu(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return 'No items selected';
|
||||||
|
case 1:
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
menuItems: this._getMenu(false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const result = this._selection.getSelection()[0] as IAppListItem;
|
||||||
|
return `${result.name}|${result.value}`;
|
||||||
|
default:
|
||||||
|
return `${selectionCount} items selected`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private _getMenu(disabled: boolean): ICommandBarItemProps[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'newItem',
|
||||||
|
text: 'Add app permissions',
|
||||||
|
iconProps: { iconName: 'CloudAdd' },
|
||||||
|
split: false,
|
||||||
|
ariaLabel: 'New',
|
||||||
|
onClick: () => { this.setState({ dialogHidden: false, isDeleteMode: false }) },
|
||||||
|
disabled: disabled,
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'upload',
|
||||||
|
text: 'Clear app permissions',
|
||||||
|
iconProps: { iconName: 'BlockedSiteSolid12' },
|
||||||
|
split: false,
|
||||||
|
onClick: () => { this.setState({ dialogHidden: false, isDeleteMode: true }) },
|
||||||
|
disabled: disabled,
|
||||||
|
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
const { items } = this.state;
|
||||||
|
return (
|
||||||
|
<Fabric>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<CommandBar className={styles.commandBar}
|
||||||
|
items={this.state.menuItems}
|
||||||
|
overflowButtonProps={this._overflowButtonProps}
|
||||||
|
ariaLabel="Use left and right arrow keys to navigate between commands"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<MarqueeSelection className={styles.listMargin} selection={this._selection}>
|
||||||
|
<DetailsList
|
||||||
|
items={items}
|
||||||
|
columns={this._columns}
|
||||||
|
setKey="set"
|
||||||
|
layoutMode={DetailsListLayoutMode.justified}
|
||||||
|
selection={this._selection}
|
||||||
|
checkboxVisibility={CheckboxVisibility.onHover}
|
||||||
|
selectionMode={SelectionMode.single}
|
||||||
|
selectionPreservedOnEmptyClick={true}
|
||||||
|
ariaLabelForSelectionColumn="Toggle selection"
|
||||||
|
ariaLabelForSelectAllCheckbox="Toggle selection for all items"
|
||||||
|
checkButtonAriaLabel="Row checkbox"
|
||||||
|
/>
|
||||||
|
</MarqueeSelection>
|
||||||
|
<div> </div>
|
||||||
|
<SitesSelectedDialog {...
|
||||||
|
{
|
||||||
|
isHidden: this.state.dialogHidden,
|
||||||
|
hideDialog: this._hideDialog,
|
||||||
|
webPartProperties: this.props.webpartProperties,
|
||||||
|
selectedApp: this.state.selectionDetails,
|
||||||
|
isDeleteMode: this.state.isDeleteMode
|
||||||
|
}} />
|
||||||
|
|
||||||
|
</Fabric >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||||
|
|
||||||
|
.listMargin{
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.commandBar {
|
||||||
|
margin-top: 20px;
|
||||||
|
button{
|
||||||
|
border: 0px solid white!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.checkPermUi{
|
||||||
|
|
||||||
|
button{
|
||||||
|
margin-top: 20px!important;
|
||||||
|
margin-bottom: 20px!important;
|
||||||
|
width: 155px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sitesSelectedManager {
|
||||||
|
|
||||||
|
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer{
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label{
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './SitesSelectedManager.module.scss';
|
||||||
|
import { ISitesSelectedManagerProps } from './ISitesSelectedManagerProps';
|
||||||
|
import { SitesSelectedApp } from './SitesSelectedApp';
|
||||||
|
|
||||||
|
export default class SitesSelectedManager extends React.Component<ISitesSelectedManagerProps, {}> {
|
||||||
|
public render(): React.ReactElement<ISitesSelectedManagerProps> {
|
||||||
|
return (
|
||||||
|
<div className={styles.sitesSelectedManager}>
|
||||||
|
<SitesSelectedApp {...this.props} />
|
||||||
|
<footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
7
samples/react-sites-selected-admin/src/webparts/sitesSelectedManager/loc/en-us.js
vendored
Normal file
7
samples/react-sites-selected-admin/src/webparts/sitesSelectedManager/loc/en-us.js
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
define([], function() {
|
||||||
|
return {
|
||||||
|
"DescriptionFieldLabel": "Webpart Title",
|
||||||
|
"ShowAboutFieldLabel":"Show Home / About Tab",
|
||||||
|
"AADGuidLabel":"Sites Selected Permission GUID"
|
||||||
|
}
|
||||||
|
});
|
10
samples/react-sites-selected-admin/src/webparts/sitesSelectedManager/loc/mystrings.d.ts
vendored
Normal file
10
samples/react-sites-selected-admin/src/webparts/sitesSelectedManager/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
declare interface ISitesSelectedManagerWebPartStrings {
|
||||||
|
DescriptionFieldLabel: string;
|
||||||
|
ShowAboutFieldLabel: string;
|
||||||
|
AADGuidLabel:string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'SitesSelectedManagerWebPartStrings' {
|
||||||
|
const strings: ISitesSelectedManagerWebPartStrings;
|
||||||
|
export = strings;
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 383 B |
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"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",
|
||||||
|
"src/**/*.tsx"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"lib"
|
||||||
|
]
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue