|
@ -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": {
|
||||
"version": "1.8.2",
|
||||
"libraryName": "myflows",
|
||||
"libraryId": "80eca57a-37fc-4b0c-a893-3ff4c0e5b9ae",
|
||||
"environment": "spo",
|
||||
"packageManager": "npm",
|
||||
"isCreatingSolution": true,
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
# SPFx My Flows Web Part
|
||||
|
||||
## Summary
|
||||
|
||||
Manage current user flows in SharePoint or Teams Tab, this web part use the msflowsdk-1.1.js
|
||||
|
||||
|
||||
|
||||
##
|
||||
![directory](/samples/js-myflows/assets/MyFlows.gif)
|
||||
|
||||
|
||||
|
||||
![directory](/samples/js-myflows/assets/Screenshot1.png)
|
||||
|
||||
![directory](/samples/js-myflows/assets/Screenshot2.png)
|
||||
|
||||
![directory](/samples/js-myflows/assets/Screenshot3.png)
|
||||
|
||||
![directory](/samples/js-myflows/assets/Screenshot4.png)
|
||||
|
||||
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/version-1.8.2-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Online](https:/dev.office.com/sharepoint)
|
||||
* [Microsoft Teams](https://products.office.com/en-US/microsoft-teams/group-chat-software)
|
||||
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||
|
||||
|
||||
## WebPart Properties
|
||||
|
||||
Property |Type|Required| comments
|
||||
--------------------|----|--------|----------
|
||||
Web Part Title | Text| no|
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Solution
|
||||
The web part Use msflowsdk-1.1.js library
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
My FLows Web Part|João Mendes
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0.0|August 13, 2019|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
|
||||
|
||||
- Clone this repository
|
||||
- in the command line run:
|
||||
- `npm install`
|
||||
- `gulp build`
|
||||
- `gulp bundle --ship`
|
||||
- `gulp package-solution --ship`
|
||||
- `Add to AppCatalog and deploy`
|
||||
|
||||
|
||||
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-myflows" />
|
||||
|
After Width: | Height: | Size: 23 MiB |
After Width: | Height: | Size: 624 KiB |
After Width: | Height: | Size: 574 KiB |
After Width: | Height: | Size: 315 KiB |
After Width: | Height: | Size: 338 KiB |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"my-flows-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/myFlows/MyFlowsWebPart.js",
|
||||
"manifest": "./src/webparts/myFlows/MyFlowsWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"MyFlowsWebPartStrings": "lib/webparts/myFlows/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": "myflows",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "myflows-client-side-solution",
|
||||
"id": "80eca57a-37fc-4b0c-a893-3ff4c0e5b9ae",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/myflows.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 gulp = require('gulp');
|
||||
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(gulp);
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "myflows",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.8.2",
|
||||
"@microsoft/sp-property-pane": "1.8.2",
|
||||
"@microsoft/sp-webpart-base": "1.8.2",
|
||||
"@microsoft/sp-lodash-subset": "1.8.2",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.8.2",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"@types/es6-promise": "0.0.33"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.8.2",
|
||||
"@microsoft/sp-tslint-rules": "1.8.2",
|
||||
"@microsoft/sp-module-interfaces": "1.8.2",
|
||||
"@microsoft/sp-webpart-workbench": "1.8.2",
|
||||
"@microsoft/rush-stack-compiler-2.9": "0.7.7",
|
||||
"gulp": "~3.9.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,39 @@
|
|||
import "./msflowsdk-1.1.js";
|
||||
import {
|
||||
SPHttpClient,
|
||||
SPHttpClientResponse,
|
||||
ISPHttpClientOptions
|
||||
} from "@microsoft/sp-http";
|
||||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
|
||||
/**
|
||||
* Services
|
||||
*/
|
||||
export default class services {
|
||||
private _context: WebPartContext;
|
||||
constructor(private context: WebPartContext) {
|
||||
this._context = this.context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets access token
|
||||
* @returns access token
|
||||
*/
|
||||
public async getAccessToken():Promise<string> {
|
||||
const body: ISPHttpClientOptions = {
|
||||
body: JSON.stringify({
|
||||
resource: "https://service.flow.microsoft.com/"
|
||||
})
|
||||
};
|
||||
|
||||
let token: SPHttpClientResponse = await this._context.spHttpClient.post(
|
||||
`${this._context.pageContext.web.absoluteUrl}/_api/SP.OAuth.Token/Acquire`,
|
||||
SPHttpClient.configurations.v1,
|
||||
body
|
||||
);
|
||||
let tokenJson = await token.json();
|
||||
return tokenJson.access_token;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "69105900-a016-43cb-9e28-9a16bb92cb87",
|
||||
"alias": "MyFlowsWebPart",
|
||||
"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","SharePointFullPage","TeamsTab"],
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "SPFx Apps" },
|
||||
"title": { "default": "My Flows" },
|
||||
"description": { "default": "My Flows" },
|
||||
"officeFabricIconFontName": "MicrosoftFlowLogo",
|
||||
"properties": {
|
||||
"title": "My Flows"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
min-height: 800px;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
border-width: 0px;
|
||||
border-style: solid;
|
||||
border-color: $ms-color-white;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: $ms-font-size-l;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 4px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.sdk{
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.titleTheme{
|
||||
@include ms-font-xl;
|
||||
@include ms-fontSize-24;
|
||||
}
|
||||
.titleWhite{
|
||||
@include ms-font-xl;
|
||||
@include ms-fontSize-24;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.error{
|
||||
@include ms-font-xl;
|
||||
@include ms-fontSize-24;
|
||||
color: red;
|
||||
margin: 15px;
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
import { Version, Guid } from "@microsoft/sp-core-library";
|
||||
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
|
||||
import {
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from "@microsoft/sp-property-pane";
|
||||
import { escape } from "@microsoft/sp-lodash-subset";
|
||||
|
||||
import styles from "./MyFlowsWebPart.module.scss";
|
||||
import * as strings from "MyFlowsWebPartStrings";
|
||||
import service from "./../../services/services";
|
||||
|
||||
export interface IMyFlowsWebPartProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export default class MyFlowsWebPart extends BaseClientSideWebPart<
|
||||
IMyFlowsWebPartProps
|
||||
> {
|
||||
private _msFlowSdk: any = null;
|
||||
private _services: service = null;
|
||||
private _guid = Guid.newGuid();
|
||||
|
||||
public constructor(props: IMyFlowsWebPartProps) {
|
||||
super();
|
||||
|
||||
// Initialize flow SDK
|
||||
this._msFlowSdk = window["MsFlowSdk"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets context
|
||||
* @returns context
|
||||
*/
|
||||
private getContext(): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let classColor = styles.titleTheme;
|
||||
if (this.context.microsoftTeams) {
|
||||
this.context.microsoftTeams.getContext(teamsContext => {
|
||||
classColor =
|
||||
teamsContext.theme !== "default"
|
||||
? styles.titleWhite
|
||||
: styles.titleTheme;
|
||||
resolve(classColor);
|
||||
});
|
||||
} else {
|
||||
resolve(classColor);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Renders my flows web part
|
||||
*/
|
||||
public async render(): Promise<void> {
|
||||
this.domElement.setAttribute("Id", `"${this._guid}"`);
|
||||
this.domElement.setAttribute("class", `"${styles.sdk}"`);
|
||||
const classColor = await this.getContext();
|
||||
this.domElement.innerHTML = `<div><label class=${classColor}>${
|
||||
this.properties.title
|
||||
}</label></div>`;
|
||||
|
||||
//
|
||||
this._services = new service(this.context);
|
||||
try {
|
||||
const token: string = await this._services.getAccessToken();
|
||||
const flowSDK = new this._msFlowSdk({
|
||||
hostName: "https://flow.microsoft.com",
|
||||
locale: this.context.pageContext.cultureInfo.currentCultureName
|
||||
});
|
||||
// Render Flow widget
|
||||
let widget: any = flowSDK.renderWidget("flows", {
|
||||
container: `"${this._guid}"`,
|
||||
sdkVersion: "1.1",
|
||||
enableOnBehalfOfTokens: true,
|
||||
debugMode: false,
|
||||
allowOptionalEvents: true,
|
||||
flowsSettings: {
|
||||
createFromBlankTemplateId: "05ed784f63df4ac7b8cbb465005d6068",
|
||||
encodedFlowsFilter: "",
|
||||
isMini: false,
|
||||
enableBusinessProcessFlow: true
|
||||
},
|
||||
templatesSettings: {
|
||||
defaultParams: "",
|
||||
category: "PowerAppsButton",
|
||||
destination: "new",
|
||||
metadataSortProperty: "",
|
||||
pageSize: 6,
|
||||
searchTerm: "",
|
||||
useServerSideProvisioning: false,
|
||||
showGoBack: true,
|
||||
enableWidgetCloseOnFlowSave: false,
|
||||
showCreateFromBlank: false,
|
||||
enableDietDesigner: false,
|
||||
showHiddenTemplates: false,
|
||||
allowCustomFlowName: false,
|
||||
oneClickCategory: "",
|
||||
dietCategory: ""
|
||||
},
|
||||
widgetStyleSettings: {
|
||||
backgroundColor: "",
|
||||
themeName: ""
|
||||
}
|
||||
});
|
||||
// Register handler
|
||||
widget.listen("GET_ACCESS_TOKEN", (requestParam, widgetDoneCallback) => {
|
||||
widgetDoneCallback(null, { token: token });
|
||||
});
|
||||
// Register handler
|
||||
widget.listen("WIDGET_READY", () => {
|
||||
console.log("The flow widget is now ready.");
|
||||
});
|
||||
} catch (error) {
|
||||
// error
|
||||
this.domElement.innerHTML = `<div><label class=${classColor}>${
|
||||
this.properties.title
|
||||
}</label></div>
|
||||
<div ><label class=${styles.error}>${error}</label></div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse("1.0");
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField("title", {
|
||||
label: strings.DescriptionFieldLabel
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Manage My Flows in SharePoint ",
|
||||
"BasicGroupName": "Properties",
|
||||
"DescriptionFieldLabel": "Title"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
declare interface IMyFlowsWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'MyFlowsWebPartStrings' {
|
||||
const strings: IMyFlowsWebPartStrings;
|
||||
export = strings;
|
||||
}
|
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-2.9/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"
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -254,7 +254,7 @@ export class EventRecurrenceInfoMonthly extends React.Component<IEventRecurrence
|
|||
* @memberof EventRecurrenceInfoMonthly
|
||||
*/
|
||||
private onWeekOrderMonthChange(ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption):void {
|
||||
this.setState({selectedWeekOrderMonth: item.text});
|
||||
this.setState({selectedWeekOrderMonth: item.key.toString()});
|
||||
this.applyRecurrence();
|
||||
}
|
||||
|
||||
|
@ -472,7 +472,7 @@ export class EventRecurrenceInfoMonthly extends React.Component<IEventRecurrence
|
|||
|
||||
</div>
|
||||
<div style={{ width: '100%', paddingTop: '10px' }}>
|
||||
<Label>Patern</Label>
|
||||
<Label>{ strings.patternLabel }</Label>
|
||||
<ChoiceGroup
|
||||
selectedKey={this.state.selectPatern}
|
||||
options={[
|
||||
|
@ -521,9 +521,9 @@ export class EventRecurrenceInfoMonthly extends React.Component<IEventRecurrence
|
|||
disabled={!this.state.disableDayOfMonth}
|
||||
options={[
|
||||
{ key: 'first', text: strings.firstLabel },
|
||||
{ key: 'second', text:strings.secondLabel},
|
||||
{ key: 'second', text: strings.secondLabel},
|
||||
{ key: 'third', text: strings.thirdLabel },
|
||||
{ key: 'fourth', text:strings.fourthLabel },
|
||||
{ key: 'fourth', text: strings.fourthLabel },
|
||||
{ key: 'last', text: strings.lastLabel },
|
||||
|
||||
]}
|
||||
|
|
|
@ -207,7 +207,7 @@ export class EventRecurrenceInfoYearly extends React.Component<IEventRecurrenceI
|
|||
* @memberof EventRecurrenceInfoYearly
|
||||
*/
|
||||
private onWeekOrderMonthChange(ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void {
|
||||
this.setState({ selectedWeekOrderMonth: item.text });
|
||||
this.setState({ selectedWeekOrderMonth: item.key.toString() });
|
||||
this.applyRecurrence();
|
||||
}
|
||||
|
||||
|
@ -232,7 +232,7 @@ export class EventRecurrenceInfoYearly extends React.Component<IEventRecurrenceI
|
|||
* @memberof EventRecurrenceInfoYearly
|
||||
*/
|
||||
private onSelectedWeekDayChange(ev: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void {
|
||||
this.setState({ selectedWeekDay: item.text });
|
||||
this.setState({ selectedWeekDay: item.key.toString() });
|
||||
this.applyRecurrence();
|
||||
}
|
||||
|
||||
|
@ -526,7 +526,7 @@ export class EventRecurrenceInfoYearly extends React.Component<IEventRecurrenceI
|
|||
]}
|
||||
/>
|
||||
</div>
|
||||
<Label styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '30px', paddingLeft: '10px' } }}>of</Label>
|
||||
<Label styles={{ root: { display: 'inline-block', verticalAlign: 'top', width: '30px', paddingLeft: '10px' } }}>{ strings.ofMonthLabel} </Label>
|
||||
<div style={{ display: 'inline-block', verticalAlign: 'top', width: '100px', paddingLeft: '5px' }}>
|
||||
<Dropdown
|
||||
selectedKey={this.state.selectedYearlyByDayMonth}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
define([], function () {
|
||||
return {
|
||||
WeeksOnLabel: "week(s) on",
|
||||
PaternLabel: "Patern",
|
||||
PaternLabel: "Pattern",
|
||||
OcurrencesLabel: "Ocurrences",
|
||||
dateRangeLabel: "Date Range",
|
||||
weekEndDay: "Weekend Day",
|
||||
|
@ -125,6 +125,7 @@ define([], function () {
|
|||
yearlyLabel: "Yearly",
|
||||
patternLabel: "Pattern",
|
||||
dateRangeLabel: "Date Range",
|
||||
occurrencesLabel: "occurrences"
|
||||
occurrencesLabel: "occurrences",
|
||||
ofMonthLabel: "of"
|
||||
}
|
||||
});
|
||||
|
|
|
@ -126,6 +126,7 @@ declare interface ICalendarWebPartStrings {
|
|||
patternLabel: string;
|
||||
dateRangeLabel: string;
|
||||
occurrencesLabel: string;
|
||||
ofMonthLabel:string;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -125,7 +125,8 @@ define([], function () {
|
|||
yearlyLabel: "Årligen",
|
||||
patternLabel: "Schema",
|
||||
dateRangeLabel: "Datumintervall",
|
||||
occurrencesLabel: "tillfällen"
|
||||
occurrencesLabel: "tillfällen",
|
||||
ofMonthLabel: "i"
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
|
||||
}
|
|
@ -2,6 +2,7 @@ import { WebPartContext } from "@microsoft/sp-webpart-base";
|
|||
import { graph } from "@pnp/graph";
|
||||
import { sp, PeoplePickerEntity, ClientPeoplePickerQueryParameters, SearchQuery, SearchResults, SearchProperty, SortDirection } from '@pnp/sp';
|
||||
import { PrincipalType } from "@pnp/sp/src/sitegroups";
|
||||
import { isRelativeUrl } from "office-ui-fabric-react";
|
||||
|
||||
|
||||
export class spservices {
|
||||
|
@ -67,6 +68,9 @@ user:string */
|
|||
SortList: [{ "Property": "LastName", "Direction": SortDirection.Ascending }],
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
return users;
|
||||
} catch (error) {
|
||||
Promise.reject(error);
|
||||
|
|
|
@ -99,6 +99,33 @@ export default class Directory extends React.Component<
|
|||
await this._searchUsers("A");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets image base64
|
||||
* @param pictureUrl
|
||||
* @returns
|
||||
*/
|
||||
private getImageBase64(pictureUrl: string):Promise<string>{
|
||||
return new Promise((resolve, reject) => {
|
||||
let image = new Image();
|
||||
image.addEventListener("load", () => {
|
||||
let tempCanvas = document.createElement("canvas");
|
||||
tempCanvas.width = image.width,
|
||||
tempCanvas.height = image.height,
|
||||
tempCanvas.getContext("2d").drawImage(image, 0, 0);
|
||||
let base64Str;
|
||||
try {
|
||||
base64Str = tempCanvas.toDataURL("image/png");
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
|
||||
resolve(base64Str);
|
||||
});
|
||||
image.src = pictureUrl;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private async _searchUsers(searchText: string) {
|
||||
searchText = searchText.trim().length > 0 ? searchText : "A";
|
||||
this.setState({
|
||||
|
@ -112,6 +139,17 @@ export default class Directory extends React.Component<
|
|||
searchText,
|
||||
this.props.searchFirstName
|
||||
);
|
||||
|
||||
if (users && users.PrimarySearchResults.length > 0){
|
||||
for (let index = 0; index < users.PrimarySearchResults.length; index++) {
|
||||
let user:any = users.PrimarySearchResults[index] ;
|
||||
if (user.PictureURL){
|
||||
user = { ...user, PictureURL: await this.getImageBase64(`/_layouts/15/userphoto.aspx?size=M&accountname=${user.WorkEmail}`)};
|
||||
users.PrimarySearchResults[index] = user ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
users:
|
||||
users && users.PrimarySearchResults
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import * as React from "react";
|
||||
import styles from "./PersonaCard.module.scss";
|
||||
import { IPersonaCardProps } from "./IPersonaCardProps";
|
||||
import { IPersonaCardState } from "./IPersonaCardState";
|
||||
import * as React from 'react';
|
||||
import styles from './PersonaCard.module.scss';
|
||||
import { IPersonaCardProps } from './IPersonaCardProps';
|
||||
import { IPersonaCardState } from './IPersonaCardState';
|
||||
import {
|
||||
Version,
|
||||
Environment,
|
||||
EnvironmentType,
|
||||
ServiceScope,
|
||||
Log,
|
||||
Text
|
||||
} from "@microsoft/sp-core-library";
|
||||
import { SPComponentLoader } from "@microsoft/sp-loader";
|
||||
Text,
|
||||
} from '@microsoft/sp-core-library';
|
||||
import { SPComponentLoader } from '@microsoft/sp-loader';
|
||||
|
||||
import {
|
||||
Persona,
|
||||
|
@ -22,13 +22,12 @@ import {
|
|||
DocumentCard,
|
||||
IDocumentCardStyles,
|
||||
DocumentCardType,
|
||||
Icon
|
||||
} from "office-ui-fabric-react";
|
||||
Icon,
|
||||
} from 'office-ui-fabric-react';
|
||||
|
||||
const EXP_SOURCE: string = "SPFxDirectory";
|
||||
const EXP_SOURCE: string = 'SPFxDirectory';
|
||||
const LIVE_PERSONA_COMPONENT_ID: string =
|
||||
"914330ee-2df2-4f6e-a858-30c23a812408";
|
||||
//const PROFILE_IMAGE_URL: string = 'https://outlook.office365.com/owa/service.svc/s/GetPersonaPhoto?email={0}&UA=0&size=HR96x96&sc=1564597822258';
|
||||
'914330ee-2df2-4f6e-a858-30c23a812408';
|
||||
|
||||
export class PersonaCard extends React.Component<
|
||||
IPersonaCardProps,
|
||||
|
@ -36,6 +35,7 @@ export class PersonaCard extends React.Component<
|
|||
> {
|
||||
constructor(props: IPersonaCardProps) {
|
||||
super(props);
|
||||
|
||||
this.state = { livePersonaCard: undefined, pictureUrl: undefined };
|
||||
}
|
||||
/**
|
||||
|
@ -78,11 +78,11 @@ export class PersonaCard extends React.Component<
|
|||
serviceScope: this.props.context.serviceScope,
|
||||
upn: this.props.profileProperties.Email,
|
||||
onCardOpen: () => {
|
||||
console.log("LivePersonaCard Open");
|
||||
console.log('LivePersonaCard Open');
|
||||
},
|
||||
onCardClose: () => {
|
||||
console.log("LivePersonaCard Close");
|
||||
}
|
||||
console.log('LivePersonaCard Close');
|
||||
},
|
||||
},
|
||||
this._PersonaCard()
|
||||
);
|
||||
|
@ -113,25 +113,25 @@ export class PersonaCard extends React.Component<
|
|||
>
|
||||
{this.props.profileProperties.WorkPhone ? (
|
||||
<div>
|
||||
<Icon iconName="Phone" style={{ fontSize: "12px" }} />
|
||||
<span style={{ marginLeft: 5, fontSize: "12px" }}>
|
||||
{" "}
|
||||
<Icon iconName="Phone" style={{ fontSize: '12px' }} />
|
||||
<span style={{ marginLeft: 5, fontSize: '12px' }}>
|
||||
{' '}
|
||||
{this.props.profileProperties.WorkPhone}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
''
|
||||
)}
|
||||
{this.props.profileProperties.Location ? (
|
||||
<div>
|
||||
<Icon iconName="Poi" style={{ fontSize: "12px" }} />
|
||||
<span style={{ marginLeft: 5, fontSize: "12px" }}>
|
||||
{" "}
|
||||
<Icon iconName="Poi" style={{ fontSize: '12px' }} />
|
||||
<span style={{ marginLeft: 5, fontSize: '12px' }}>
|
||||
{' '}
|
||||
{this.props.profileProperties.Location}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
''
|
||||
)}
|
||||
</Persona>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,21 @@
|
|||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false
|
||||
"isDomainIsolated": false,
|
||||
"webApiPermissionRequests": [
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "User.Read.All"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Directory.Read.All"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Directory.AccessAsUser.All"
|
||||
}
|
||||
]
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-manage-sitedesigns.sppkg"
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
.vscode
|
||||
/webpart/package-lock.json
|
|
@ -0,0 +1,161 @@
|
|||
# Site Provisioning Manager Web Part
|
||||
|
||||
## Summary
|
||||
This sample shows how you can manage site provisioning by calling Azure functions.
|
||||
|
||||
You can also find out how you can use React Hooks to manage the state of your application and share data across all components.
|
||||
|
||||
|
||||
![react-provisioning-manager](./assets/screenshot.gif)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/version-1.9-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework](https://dev.office.com/sharepoint)
|
||||
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-site-provisioning-manager | Ramin Ahmadi
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|August 14, 2019|Initial release
|
||||
|
||||
## Features
|
||||
This sample illustrates the following concepts on top of the SharePoint Framework:
|
||||
|
||||
* Using React Hooks.
|
||||
* Using aadHttpClientFactory to call Azure functions.
|
||||
* PnP/graph to call Microsoft Graph Api.
|
||||
|
||||
## Configure Azure Function
|
||||
|
||||
### Create a self signed certificate
|
||||
|
||||
1. Run below command using Create-SelfSignedCertificate.ps1 in powershell-scripts folder.
|
||||
|
||||
```
|
||||
.\Create-SelfSignedCertificate.ps1 -CommonName "NAME" -StartDate 2019-08-11 -EndDate 2025-08-11 -Password (ConvertTo-SecureString -String "PASSWORD" -AsPlainText -Force)
|
||||
```
|
||||
|
||||
> The dates are provided in US date format: YYYY-MM-dd
|
||||
> Don't forget to update the PASSWORD and NAME.
|
||||
|
||||
### Publishing the Azure function app
|
||||
|
||||
Follow below steps in order to publish the functions:
|
||||
|
||||
1. Open Provisioning App solution with Visual Studio 2017/2019.
|
||||
2. Copy the .pfx certificate you generated under the Cert folder.
|
||||
3. Open ProvisioningApp.csproj in a text editor and make sure your cert name is included. If not, replace provisioningapp.pfx with your cert file name.
|
||||
2. In Solution Explorer, right-click the project and select Publish.
|
||||
3. In the Pick a publish target dialog, use the publish options as specified in the table below the image:
|
||||
|
||||
![publish-profile](./assets/functions-visual-studio-publish-profile.png)
|
||||
|
||||
4. Select Publish. If you haven't already signed-in to your Azure account from Visual Studio, select Sign-in.
|
||||
5. In the App Service: Create new dialog, enter the hosting settings.
|
||||
6. Select Create to create a function app and related resources in Azure with these settings and deploy your function project code.
|
||||
|
||||
## Setting up an Azure AD app for app-only access
|
||||
|
||||
### Create a new app registration in Azure AD
|
||||
|
||||
1. Open Azure Portal https://portal.azure.com.
|
||||
2. Click on Azure Active Directory.
|
||||
3. Click on App registrations.
|
||||
4. Click on New registration.
|
||||
5. Give youre registration a name.
|
||||
6. Click Register.
|
||||
|
||||
### Add your certificate to the app registration
|
||||
|
||||
1. Open Azure Portal https://portal.azure.com.
|
||||
2. Select Azure Active Director, App Registration and then the App your created in previous steps.
|
||||
3. Click on "Certificates & secrets".
|
||||
4. Click on the "Upload certificate" button.
|
||||
5. Select the .CER file you generated earlier and click on "Add" to upload it.
|
||||
|
||||
### API permissions
|
||||
|
||||
1. In the app registration we created earlier, click on API Permissions.
|
||||
2. Click on the "Add a permission" button.
|
||||
3. Choose the following permissions:
|
||||
* SharePoint -> Application permissions -> Sites -> Sites.FullControl.All
|
||||
4. Click Add permissions to save
|
||||
5. Click Grant admin consent for the permissions to come into effect.
|
||||
![API Permissions](./assets/api-permissions.png)
|
||||
|
||||
### Add the user_impersonation scope
|
||||
|
||||
Still in your Azure AD app, do the following:
|
||||
|
||||
1. Click on Expose API.
|
||||
2. Click on Add scope
|
||||
3. Approve the suggested URL or change it, if you like.
|
||||
4. Fill in the following info:
|
||||
- Scope name: `user_impersonation`
|
||||
- Admin consent display name: `Access YourAzureAdAppDisplayName`
|
||||
- Admin consent description: `Allow the application to access YourAzureAdAppDisplayName on behalf of the signed-in user.`
|
||||
3. Press Add scope to save.
|
||||
|
||||
### Securing the Azure function app
|
||||
|
||||
1. Open Azure Portal https://portal.azure.com.
|
||||
2. Click App Services and find the app you created earlier.
|
||||
3. Click "Platform features" tab.
|
||||
4. Under Networking, click "Authentication / Authorization".
|
||||
5. In the option “App Service Authentication”, select “ON”.
|
||||
6. For "Action to take when request is not authenticated" option, select “Log in with Azure Active Directory”.
|
||||
7. Under “Authentication Providers”, select “Azure Active Directory”.
|
||||
8. Select “Management mode” as Express.
|
||||
9. Select the Azure AD app we registered earlier.
|
||||
10. Click OK and then Save.
|
||||
|
||||
### Enable CORS on Azure Function
|
||||
|
||||
1. Click Platform features.
|
||||
2. Under API, click CORS.
|
||||
3. Specify the Office 365 tenant domain url and SharePoint local workbench url.
|
||||
4. Click Save.
|
||||
|
||||
![CORS Settings](./assets/functions-CORS-settings.PNG)
|
||||
|
||||
### Update App Settings
|
||||
|
||||
1. Go the `App Settings` page of the Azure functions.
|
||||
2. Create new key/value entries under ‘App settings’ as per the following table:
|
||||
|
||||
Key|Value|Note
|
||||
---|-----|----
|
||||
CERTIFICATE| .pfx file name | you should copy .pfx file in Cert folder
|
||||
PASSWORD| Password you set for the certificate file
|
||||
CLIENTID| Application Registration Client ID| you can find the client id from overview tab
|
||||
TENANT| e.x. contoso.onmicrosoft.com
|
||||
|
||||
## Installing the web part
|
||||
|
||||
In the package-solution.json, replace the value of `resource` (under `webApiPermissionRequests`) with the name of your Azure AD app registration.
|
||||
|
||||
On the command line run (when in `webparts` dir):
|
||||
- `npm install`
|
||||
- `gulp bundle --ship`
|
||||
- `gulp package-solution --ship`
|
||||
- Drop the .sppkg file under `sharepoint\solution` to your tenant app catalog.
|
||||
- Approve the API permissions via the new SharePoint admin center.
|
||||
|
||||
## Configuring the web part on a page
|
||||
|
||||
Open the web part configurations and set the values:
|
||||
1. Application Id/EndPoint: the client ID of the Azure AD app registration used for authentication
|
||||
2. Get provisioning function URL: Go to the Azure functions in Azure portal and click on "GetProvisioningTemplate" and then "Get function Url". Copy-paste that value in this field.
|
||||
3. Apply provisioning function URL: Go to the Azure functions in Azure portal and click on "ApplyProvisioningTemplate" and then "Get function Url". Copy-paste that value in this field.
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-site-provisioning-manager" />
|
After Width: | Height: | Size: 97 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 585 KiB |
|
@ -0,0 +1,25 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29123.88
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProvisioningApp", "ProvisioningApp\ProvisioningApp.csproj", "{8CB1D773-D3D0-4B37-B310-BA94F2476D75}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{8CB1D773-D3D0-4B37-B310-BA94F2476D75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8CB1D773-D3D0-4B37-B310-BA94F2476D75}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8CB1D773-D3D0-4B37-B310-BA94F2476D75}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8CB1D773-D3D0-4B37-B310-BA94F2476D75}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {5374BAB8-D4AB-4D3B-8A38-1C351CF3BB4B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
264
samples/react-site-provisioning-manager/azure-function/ProvisioningApp/ProvisioningApp/.gitignore
vendored
Normal file
|
@ -0,0 +1,264 @@
|
|||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# Azure Functions localsettings file
|
||||
local.settings.json
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# DNX
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
#*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignoreable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
orleans.codegen.cs
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
|
@ -0,0 +1 @@
|
|||
Copy your pfx file here before publish the function.
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ProvisioningApp.Constants
|
||||
{
|
||||
public static class Configs
|
||||
{
|
||||
public const string FileName = "PnPProvisioningTemplate.xml";
|
||||
public const string clientIdKey = "CLIENTID";
|
||||
public const string certificatePathKey = "CERTIFICATE";
|
||||
public const string passwordKey = "PASSWORD";
|
||||
public const string tenantKey = "TENANT";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Azure.WebJobs.Host;
|
||||
using Microsoft.SharePoint.Client;
|
||||
using OfficeDevPnP.Core.Framework.Provisioning.Model;
|
||||
using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml;
|
||||
using ProvisioningApp.Models;
|
||||
using ProvisioningApp.Utils;
|
||||
|
||||
namespace ProvisioningApp.Functions
|
||||
{
|
||||
public static class ApplyProvisioningTemplate
|
||||
{
|
||||
[FunctionName("ApplyProvisioningTemplate")]
|
||||
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequestMessage req, TraceWriter log, ExecutionContext context)
|
||||
{
|
||||
log.Info("C# HTTP trigger function processed a request.");
|
||||
|
||||
try
|
||||
{
|
||||
var requestBody = await req.Content.ReadAsAsync<ApplyProvisioningInfo>();
|
||||
|
||||
if (requestBody == null)
|
||||
return req.CreateResponse(HttpStatusCode.BadRequest);
|
||||
|
||||
var ctx = Helper.GetADAppOnlyContext(requestBody.WebUrl, context.FunctionAppDirectory);
|
||||
using (ctx)
|
||||
{
|
||||
Web web = ctx.Web;
|
||||
ctx.Load(web, w => w.Title);
|
||||
ctx.ExecuteQueryRetry();
|
||||
|
||||
// Configure the XML file system provider
|
||||
XMLTemplateProvider provider =
|
||||
new XMLFileSystemTemplateProvider(Path.GetTempPath(), "");
|
||||
|
||||
byte[] byteArray = Encoding.ASCII.GetBytes(requestBody.Template);
|
||||
MemoryStream stream = new MemoryStream(byteArray);
|
||||
|
||||
// Load the template from the XML stored copy
|
||||
ProvisioningTemplate template = provider.GetTemplate(stream);
|
||||
|
||||
// We can also use Apply-PnPProvisioningTemplate
|
||||
web.ApplyProvisioningTemplate(template);
|
||||
}
|
||||
|
||||
return req.CreateErrorResponse(HttpStatusCode.OK, "Done!");
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return req.CreateErrorResponse(System.Net.HttpStatusCode.InternalServerError, ex.Message);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Azure.WebJobs.Host;
|
||||
using Microsoft.SharePoint.Client;
|
||||
using OfficeDevPnP.Core;
|
||||
using OfficeDevPnP.Core.Framework.Provisioning.Connectors;
|
||||
using OfficeDevPnP.Core.Framework.Provisioning.Model;
|
||||
using OfficeDevPnP.Core.Framework.Provisioning.ObjectHandlers;
|
||||
using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml;
|
||||
using ProvisioningApp.Models;
|
||||
using ProvisioningApp.Constants;
|
||||
using ProvisioningApp.Utils;
|
||||
using System.IO;
|
||||
|
||||
namespace ProvisioningApp
|
||||
{
|
||||
public static class GetProvisioningTemplate
|
||||
{
|
||||
[FunctionName("GetProvisioningTemplate")]
|
||||
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequestMessage req, TraceWriter log, ExecutionContext context)
|
||||
{
|
||||
log.Info("C# HTTP trigger function processed a request.");
|
||||
|
||||
try
|
||||
{
|
||||
var requestBody = await req.Content.ReadAsAsync<LoadProvisioningInfo>();
|
||||
|
||||
if (requestBody == null)
|
||||
return req.CreateResponse(System.Net.HttpStatusCode.BadRequest);
|
||||
|
||||
GenerateProvisioningTemplate(requestBody,context.FunctionAppDirectory);
|
||||
log.Info("Template has been created");
|
||||
var xDocument = XDocument.Load($"{Path.GetTempPath()}\\{Configs.FileName}");
|
||||
// convert the xml into string
|
||||
string xml = xDocument.ToString();
|
||||
var result = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
|
||||
result.Content = new ByteArrayContent(System.Text.Encoding.UTF8.GetBytes(xml));
|
||||
result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/xml");
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return req.CreateErrorResponse(System.Net.HttpStatusCode.InternalServerError,ex.Message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void GenerateProvisioningTemplate(LoadProvisioningInfo info, string appDirectory)
|
||||
{
|
||||
var context = Helper.GetADAppOnlyContext(info.WebUrl, appDirectory);
|
||||
|
||||
using (context)
|
||||
{
|
||||
Web web = context.Web;
|
||||
context.Load(web, w => w.Title);
|
||||
context.ExecuteQueryRetry();
|
||||
ProvisioningTemplateCreationInformation ptci
|
||||
= new ProvisioningTemplateCreationInformation(context.Web);
|
||||
|
||||
// Create FileSystemConnector to store a temporary copy of the template
|
||||
ptci.FileConnector = new FileSystemConnector(Path.GetTempPath(), "");
|
||||
ptci.PersistBrandingFiles = true;
|
||||
|
||||
ptci.HandlersToProcess = info.Handlers;
|
||||
// Execute actual extraction of the template
|
||||
ProvisioningTemplate template = context.Web.GetProvisioningTemplate(ptci);
|
||||
|
||||
// We can serialize this template to save and reuse it
|
||||
// Optional step
|
||||
XMLTemplateProvider provider =
|
||||
new XMLFileSystemTemplateProvider(Path.GetTempPath(), "");
|
||||
provider.SaveAs(template, Configs.FileName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ProvisioningApp.Models
|
||||
{
|
||||
public class ApplyProvisioningInfo
|
||||
{
|
||||
public string WebUrl { get; set; }
|
||||
|
||||
public string Template { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using OfficeDevPnP.Core.Framework.Provisioning.Model;
|
||||
namespace ProvisioningApp.Models
|
||||
{
|
||||
public class LoadProvisioningInfo
|
||||
{
|
||||
public string WebUrl { get; set; }
|
||||
|
||||
public Handlers Handlers { get; set; }
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<UserName>$Site-Provisioning-App</UserName>
|
||||
<WebPublishMethod>ZipDeploy</WebPublishMethod>
|
||||
<PublishProvider>AzureWebSite</PublishProvider>
|
||||
<LastUsedBuildConfiguration>Debug</LastUsedBuildConfiguration>
|
||||
<LastUsedPlatform>Any CPU</LastUsedPlatform>
|
||||
<SiteUrlToLaunchAfterPublish>https://site-provisioning-app.azurewebsites.net</SiteUrlToLaunchAfterPublish>
|
||||
<LaunchSiteAfterPublish>False</LaunchSiteAfterPublish>
|
||||
<ResourceId>/subscriptions/ab4ec88f-687a-4544-847c-84d463e8538b/resourceGroups/Lab/providers/Microsoft.Web/sites/Site-Provisioning-App</ResourceId>
|
||||
<_SavePWD>True</_SavePWD>
|
||||
<PublishUrl>https://site-provisioning-app.scm.azurewebsites.net/</PublishUrl>
|
||||
<TargetFramework>net461</TargetFramework>
|
||||
<SkipExtraFilesOnServer>True</SkipExtraFilesOnServer>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -0,0 +1,29 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net461</TargetFramework>
|
||||
<AzureFunctionsVersion>v1</AzureFunctionsVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.24" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="SharePointPnPCoreOnline" Version="3.12.1908" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Cert\provisioningapp.pfx">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="host.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="local.settings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Cert\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,35 @@
|
|||
using Microsoft.SharePoint.Client;
|
||||
using OfficeDevPnP.Core;
|
||||
using System;
|
||||
using ProvisioningApp.Constants;
|
||||
|
||||
namespace ProvisioningApp.Utils
|
||||
{
|
||||
public static class Helper
|
||||
{
|
||||
public static ClientContext GetADAppOnlyContext(string siteUrl, string appDirectory)
|
||||
{
|
||||
var authMgr = new AuthenticationManager();
|
||||
|
||||
string certificateName = Environment.GetEnvironmentVariable(Configs.certificatePathKey);
|
||||
string password = Environment.GetEnvironmentVariable(Configs.passwordKey);
|
||||
string clientId = Environment.GetEnvironmentVariable(Configs.clientIdKey);
|
||||
string tenant = Environment.GetEnvironmentVariable(Configs.tenantKey);
|
||||
string certificatePath = $"{appDirectory}\\Cert\\{certificateName}";
|
||||
|
||||
if (string.IsNullOrEmpty(clientId))
|
||||
throw new ArgumentException($"Missing required environment variable '{Configs.clientIdKey}'");
|
||||
|
||||
if (string.IsNullOrEmpty(certificateName))
|
||||
throw new ArgumentException($"Missing required environment variable '{Configs.certificatePathKey}'");
|
||||
|
||||
if (string.IsNullOrEmpty(password))
|
||||
throw new ArgumentException($"Missing required environment variable '{Configs.passwordKey}'");
|
||||
|
||||
if (string.IsNullOrEmpty(tenant))
|
||||
throw new ArgumentException($"Missing required environment variable '{Configs.tenantKey}'");
|
||||
|
||||
return authMgr.GetAzureADAppOnlyAuthenticatedContext(siteUrl, clientId, tenant, certificatePath, password);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
|
@ -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.9.1",
|
||||
"libraryName": "react-site-provisioning-manager",
|
||||
"libraryId": "5df6f62a-b141-4997-8944-029a1fd73237",
|
||||
"packageManager": "npm",
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"site-provisioning-manager-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/siteProvisioningManager/SiteProvisioningManagerWebPart.js",
|
||||
"manifest": "./src/webparts/siteProvisioningManager/SiteProvisioningManagerWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"SiteProvisioningManagerWebPartStrings": "lib/webparts/siteProvisioningManager/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": "react-site-provisioning-manager",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-site-provisioning-manager-client-side-solution",
|
||||
"id": "5df6f62a-b141-4997-8944-029a1fd73237",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"webApiPermissionRequests": [{
|
||||
"resource": "Site-Provisioning-App",
|
||||
"scope": "user_impersonation"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Directory.Read.All"
|
||||
}],
|
||||
"isDomainIsolated": false
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-site-provisioning-manager.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 gulp = require('gulp');
|
||||
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(gulp);
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"name": "react-site-provisioning-manager",
|
||||
"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/sp-core-library": "1.9.1",
|
||||
"@microsoft/sp-lodash-subset": "1.9.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.9.1",
|
||||
"@microsoft/sp-webpart-base": "1.9.1",
|
||||
"@pnp/common": "^1.3.4",
|
||||
"@pnp/graph": "^1.3.4",
|
||||
"@pnp/logging": "^1.3.4",
|
||||
"@pnp/odata": "^1.3.4",
|
||||
"@pnp/sp": "^1.3.4",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/react": "16.8.8",
|
||||
"@types/react-dom": "16.8.3",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"office-ui-fabric-react": "6.189.2",
|
||||
"react": "16.8.5",
|
||||
"react-dom": "16.8.5"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "16.8.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.9.1",
|
||||
"@microsoft/sp-tslint-rules": "1.9.1",
|
||||
"@microsoft/sp-module-interfaces": "1.9.1",
|
||||
"@microsoft/sp-webpart-workbench": "1.9.1",
|
||||
"@microsoft/rush-stack-compiler-2.9": "0.7.16",
|
||||
"gulp": "~3.9.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,67 @@
|
|||
export interface IHandlers{
|
||||
All:boolean;
|
||||
AuditSettings:boolean;
|
||||
ComposedLook:boolean;
|
||||
CustomActions:boolean;
|
||||
ExtensibilityProviders:boolean;
|
||||
Features:boolean;
|
||||
Fields:boolean;
|
||||
Files:boolean;
|
||||
Lists:boolean;
|
||||
Pages:boolean;
|
||||
Publishing:boolean;
|
||||
RegionalSettings:boolean;
|
||||
SearchSettings:boolean;
|
||||
SitePolicy:boolean;
|
||||
SupportedUILanguages:boolean;
|
||||
TermGroups:boolean;
|
||||
Workflows:boolean;
|
||||
SiteSecurity:boolean;
|
||||
ContentTypes:boolean;
|
||||
PropertyBagEntries:boolean;
|
||||
PageContents:boolean;
|
||||
WebSettings:boolean;
|
||||
Navigation:boolean;
|
||||
ImageRenditions:boolean;
|
||||
ApplicationLifecycleManagement:boolean;
|
||||
Tenant:boolean;
|
||||
WebApiPermissions:boolean;
|
||||
SiteHeader:boolean;
|
||||
SiteFooter:boolean;
|
||||
Theme:boolean;
|
||||
}
|
||||
|
||||
const defaultHandlerValues:IHandlers={
|
||||
All:false,
|
||||
AuditSettings:false,
|
||||
ComposedLook:false,
|
||||
CustomActions:false,
|
||||
ExtensibilityProviders:false,
|
||||
Features:false,
|
||||
Fields:false,
|
||||
Files:false,
|
||||
Lists:false,
|
||||
Pages:false,
|
||||
Publishing:false,
|
||||
RegionalSettings:false,
|
||||
SearchSettings:false,
|
||||
SitePolicy:false,
|
||||
SupportedUILanguages:false,
|
||||
TermGroups:false,
|
||||
Workflows:false,
|
||||
SiteSecurity:false,
|
||||
ContentTypes:false,
|
||||
PropertyBagEntries:false,
|
||||
PageContents:false,
|
||||
WebSettings:false,
|
||||
Navigation:false,
|
||||
ImageRenditions:false,
|
||||
ApplicationLifecycleManagement:false,
|
||||
Tenant:false,
|
||||
WebApiPermissions:false,
|
||||
SiteFooter:false,
|
||||
SiteHeader:false,
|
||||
Theme:false
|
||||
};
|
||||
|
||||
export default defaultHandlerValues;
|
|
@ -0,0 +1,71 @@
|
|||
import { AadHttpClient, IHttpClientOptions, HttpClientResponse } from "@microsoft/sp-http";
|
||||
import httpHeaders from "./headers";
|
||||
import { graph } from "@pnp/graph";
|
||||
import { sp } from "@pnp/sp";
|
||||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
|
||||
export default class AppService {
|
||||
private ADMIN_ROLETEMPLATE_ID = "62e90394-69f5-4237-9190-012177145e10"; // Global Admin TemplateRoleId
|
||||
|
||||
private requestOptions: IHttpClientOptions = {
|
||||
headers: httpHeaders,
|
||||
};
|
||||
|
||||
constructor(private spfxContext: WebPartContext,
|
||||
private httpClient: AadHttpClient,
|
||||
private getProvisioningTemplateUrl,
|
||||
private applyProvisioningTemplateUrl) {
|
||||
// Setuo Context to PnPjs and MSGraph
|
||||
sp.setup({
|
||||
spfxContext: this.spfxContext
|
||||
});
|
||||
|
||||
graph.setup({
|
||||
spfxContext: this.spfxContext
|
||||
});
|
||||
|
||||
}
|
||||
// Check if user is Global Admin
|
||||
public async checkUserIsGlobalAdmin(): Promise<boolean> {
|
||||
return graph.me.memberOf.get().then(roles =>{
|
||||
for (const myDirRolesAndGroup of roles) {
|
||||
if (myDirRolesAndGroup.id && myDirRolesAndGroup.id === this.ADMIN_ROLETEMPLATE_ID) { // roleTemplateId for glabal Admin
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}).catch( e => {return false;});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user is a site admin
|
||||
*/
|
||||
public async IsSiteOwner(): Promise<boolean> {
|
||||
return sp.web.currentUser.get().then(user => {
|
||||
return user.IsSiteAdmin;
|
||||
}).catch((error: any) => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public async GetProvisioningTemplate(url: string, handlers: string): Promise<HttpClientResponse> {
|
||||
this.requestOptions.body = `{ WebUrl: '${url}', Handlers: '${handlers}' }`;
|
||||
return this.httpClient.post(
|
||||
this.getProvisioningTemplateUrl,
|
||||
AadHttpClient.configurations.v1,
|
||||
this.requestOptions,
|
||||
);
|
||||
}
|
||||
|
||||
public async ApplyProvisioningTemplate(url: string, template: string): Promise<HttpClientResponse> {
|
||||
this.requestOptions.body = `{ WebUrl: '${url}', Template: '${template}' }`;
|
||||
return this.httpClient.post(
|
||||
this.applyProvisioningTemplateUrl,
|
||||
AadHttpClient.configurations.v1,
|
||||
this.requestOptions,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
const headers : Headers= new Headers();
|
||||
headers.append("Accept","application/json");
|
||||
headers.append("Content-type","application/json");
|
||||
export default headers;
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "c2fdd0d3-2636-42e9-9f39-c2ee831b0c5c",
|
||||
"alias": "SiteProvisioningManagerWebPart",
|
||||
"componentType": "WebPart",
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
"requiresCustomScript": false,
|
||||
"supportedHosts": ["SharePointWebPart"],
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "Site Provisioning Manager" },
|
||||
"description": { "default": "Get or apply PnP provisioning templates" },
|
||||
"officeFabricIconFontName": "CustomizeToolbar",
|
||||
"properties": {
|
||||
"description": "Site Provisioning Manager",
|
||||
"ApplicationId": "",
|
||||
"GetTemplateFunctionUrl": "",
|
||||
"ApplyTemplateFunctionUrl": ""
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
|
||||
import * as strings from 'SiteProvisioningManagerWebPartStrings';
|
||||
import SiteProvisioningWebPart from './components/App/App';
|
||||
import { IAppProps } from './components/App/IAppProps';
|
||||
import { AadHttpClient } from '@microsoft/sp-http';
|
||||
import AppService from '../../services/appService';
|
||||
|
||||
export interface ISiteProvisioningManagerWebPartProps {
|
||||
ApplicationId: string;
|
||||
GetTemplateFunctionUrl: string;
|
||||
ApplyTemplateFunctionUrl: string;
|
||||
}
|
||||
|
||||
export default class SiteProvisioningManagerWebPart extends BaseClientSideWebPart<ISiteProvisioningManagerWebPartProps> {
|
||||
|
||||
private appService: AppService;
|
||||
private aadClient: AadHttpClient;
|
||||
public onInit(): Promise<void> {
|
||||
return super.onInit().then(async _ => {
|
||||
const { GetTemplateFunctionUrl, ApplyTemplateFunctionUrl } = this.properties;
|
||||
|
||||
const clientId: string = this.properties.ApplicationId;
|
||||
this.aadClient = await this.context.aadHttpClientFactory.getClient(clientId);
|
||||
this.appService = new AppService(this.context, this.aadClient, GetTemplateFunctionUrl, ApplyTemplateFunctionUrl);
|
||||
});
|
||||
|
||||
}
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IAppProps> = React.createElement(
|
||||
SiteProvisioningWebPart,
|
||||
{
|
||||
appService: this.appService,
|
||||
webUrl: this.context.pageContext.web.absoluteUrl
|
||||
}
|
||||
);
|
||||
|
||||
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: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField('ApplicationId', {
|
||||
label: strings.ClientIdFieldLabel
|
||||
}),
|
||||
PropertyPaneTextField('GetTemplateFunctionUrl', {
|
||||
label: strings.GetProvisioningUrlFieldLabel
|
||||
}),
|
||||
PropertyPaneTextField('ApplyTemplateFunctionUrl', {
|
||||
label: strings.ApplyProvisioningUrlFieldLabel
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||
|
||||
.siteProvisioningWebPart {
|
||||
.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;
|
||||
}
|
||||
.loadingImage{
|
||||
width: 150px;
|
||||
height: 140px;
|
||||
}
|
||||
.column {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg6;
|
||||
@include ms-sm6;
|
||||
@include ms-md6;
|
||||
@include ms-xl6;
|
||||
}
|
||||
.handlerCheckbox{
|
||||
margin-top:5px;
|
||||
}
|
||||
.topMargin{
|
||||
margin-top:10px;
|
||||
}
|
||||
.pivotContainer{
|
||||
margin-left:10px;
|
||||
}
|
||||
.flexRow{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
.provisioningButton{
|
||||
margin-top:10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.error{
|
||||
margin-top:10px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import * as React from 'react';
|
||||
import styles from './App.module.scss';
|
||||
import { IAppProps } from './IAppProps';
|
||||
import AppContext,{IMessageBarSettings} from "./AppContext";
|
||||
import { MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
|
||||
import AppContent from "./AppContent";
|
||||
import * as Strings from "SiteProvisioningManagerWebPartStrings";
|
||||
|
||||
const App: React.FC<IAppProps> = (props) => {
|
||||
const {webUrl,appService} = props;
|
||||
const [isGlobalAdmin,setIsGlobalAdmin] = React.useState(false);
|
||||
const [isSiteOwner,setIsSiteOwner] = React.useState(false);
|
||||
const [isLoading,setIsLoading] = React.useState(false);
|
||||
const [messageBarSettings,setMessageBarSettings] =React.useState({
|
||||
message:"",
|
||||
type:MessageBarType.info,
|
||||
visible:false
|
||||
} as IMessageBarSettings);
|
||||
|
||||
|
||||
const updateMessageBarSettings = (settings:IMessageBarSettings)=>{
|
||||
setMessageBarSettings(settings);
|
||||
};
|
||||
|
||||
const toggleLoading = (visibleLoading:boolean)=>{
|
||||
setIsLoading(visibleLoading);
|
||||
};
|
||||
|
||||
React.useEffect(()=>{
|
||||
let didCancel = false;
|
||||
|
||||
const fetchIsGloablAdmin = async ()=>{
|
||||
const globalAdmin = await appService.checkUserIsGlobalAdmin();
|
||||
if (!didCancel) {
|
||||
setIsGlobalAdmin(globalAdmin);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const fetchIsSiteOwner = async ()=>{
|
||||
const siteOwner = await appService.IsSiteOwner();
|
||||
if (!didCancel) {
|
||||
setIsSiteOwner(siteOwner);
|
||||
if(!siteOwner){
|
||||
setMessageBarSettings({
|
||||
message: Strings.ErrorMessageUserNotAdmin,
|
||||
type: MessageBarType.error,
|
||||
visible: false
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchIsGloablAdmin();
|
||||
fetchIsSiteOwner();
|
||||
|
||||
return ()=>{didCancel=true;};
|
||||
},[]);
|
||||
|
||||
return (
|
||||
<div className={styles.siteProvisioningWebPart}>
|
||||
<AppContext.Provider value={{
|
||||
appService,
|
||||
isGlobalAdmin,
|
||||
isSiteOwner,
|
||||
webUrl,
|
||||
messageBarSettings,
|
||||
isLoading,
|
||||
toggleLoading,
|
||||
updateMessageBarSettings
|
||||
} }>
|
||||
<AppContent/>
|
||||
</AppContext.Provider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
|
@ -0,0 +1,33 @@
|
|||
import * as React from 'react';
|
||||
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
|
||||
import Loading from '../Loading/Loading';
|
||||
import { Pivot, PivotItem, PivotLinkSize } from 'office-ui-fabric-react/lib/Pivot';
|
||||
import GetProvisioningTemplate from '../GetProvisioningTemplate/GetProvisioningTemplate';
|
||||
import ApplyProvisioningTemplate from '../ApplyProvisioningTemplate/ApplyProvisioningTemplate';
|
||||
import AppContext from './AppContext';
|
||||
import * as Strings from "SiteProvisioningManagerWebPartStrings";
|
||||
import styles from './App.module.scss';
|
||||
|
||||
const AppContent = () => {
|
||||
const ctx = React.useContext(AppContext);
|
||||
const {isLoading,messageBarSettings:{type,message,visible}} = ctx;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div hidden={!visible} className={styles.topMargin}>
|
||||
<MessageBar messageBarType={type}>{message}</MessageBar>
|
||||
</div>
|
||||
<Loading hidden={!isLoading} />
|
||||
<Pivot linkSize={PivotLinkSize.large} hidden={isLoading}>
|
||||
<PivotItem headerText={Strings.GetTemplateLabel}>
|
||||
<GetProvisioningTemplate />
|
||||
</PivotItem>
|
||||
<PivotItem headerText={Strings.ApplyTemplateLable}>
|
||||
<ApplyProvisioningTemplate />
|
||||
</PivotItem>
|
||||
</Pivot>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppContent;
|
|
@ -0,0 +1,23 @@
|
|||
import * as React from 'react';
|
||||
import { MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
|
||||
import AppService from '../../../../services/appService';
|
||||
|
||||
export interface IMessageBarSettings{
|
||||
visible:boolean;
|
||||
message: string;
|
||||
type: MessageBarType;
|
||||
}
|
||||
|
||||
interface IAppContextInterface {
|
||||
appService: AppService;
|
||||
isGlobalAdmin: boolean;
|
||||
isSiteOwner: boolean;
|
||||
webUrl: string;
|
||||
messageBarSettings: IMessageBarSettings;
|
||||
isLoading:boolean;
|
||||
toggleLoading: (visible:boolean)=>void;
|
||||
updateMessageBarSettings: (settings:IMessageBarSettings)=>void;
|
||||
}
|
||||
|
||||
const AppContext = React.createContext<IAppContextInterface | null>(null);
|
||||
export default AppContext;
|
|
@ -0,0 +1,6 @@
|
|||
import AppService from "../../../../services/appService";
|
||||
|
||||
export interface IAppProps {
|
||||
appService: AppService;
|
||||
webUrl:string;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import * as React from 'react';
|
||||
import styles from "../App/App.module.scss";
|
||||
import { TextField } from 'office-ui-fabric-react/lib/TextField';
|
||||
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
|
||||
import { HttpClientResponse } from '@microsoft/sp-http';
|
||||
import AppContext from "../App/AppContext";
|
||||
import { MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
|
||||
import * as Strings from "SiteProvisioningManagerWebPartStrings";
|
||||
|
||||
const SiteProvisioningTemplate = () => {
|
||||
const ctx = React.useContext(AppContext);
|
||||
const [template, setTemplate] = React.useState("");
|
||||
const [webUrl, setWebUrl] = React.useState(ctx.webUrl);
|
||||
const isNotAdmin = !ctx.isGlobalAdmin || !ctx.isSiteOwner;
|
||||
const applyTemplate = async () => {
|
||||
ctx.toggleLoading(true);
|
||||
ctx.updateMessageBarSettings({
|
||||
message: "",
|
||||
type: MessageBarType.error,
|
||||
visible: false
|
||||
});
|
||||
|
||||
const response: HttpClientResponse = await ctx.appService.ApplyProvisioningTemplate(webUrl, template);
|
||||
const responseText = await response.text();
|
||||
if (response.status === 200)
|
||||
ctx.updateMessageBarSettings({
|
||||
message: Strings.SuccessMessage,
|
||||
type: MessageBarType.success,
|
||||
visible: true
|
||||
});
|
||||
else
|
||||
ctx.updateMessageBarSettings({
|
||||
message: responseText,
|
||||
type: MessageBarType.error,
|
||||
visible: true
|
||||
});
|
||||
|
||||
ctx.toggleLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.pivotContainer}>
|
||||
<div hidden={ctx.isLoading}>
|
||||
<TextField disabled={!ctx.isGlobalAdmin} label="Site URL" value={webUrl} onChanged={(value) => setWebUrl(value)} />
|
||||
<TextField disabled={!isNotAdmin} label="Template" value={template} onChanged={(value) => setTemplate(value)} multiline rows={20} />
|
||||
<div className={styles.topMargin}>
|
||||
<PrimaryButton disabled={!isNotAdmin} text="Apply Template" onClick={applyTemplate} className={styles.provisioningButton} />
|
||||
<DefaultButton disabled={!isNotAdmin} text="Reset" onClick={() => setTemplate("")} className={styles.provisioningButton} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SiteProvisioningTemplate;
|
After Width: | Height: | Size: 546 KiB |
|
@ -0,0 +1,109 @@
|
|||
import * as React from "react";
|
||||
import { TextField } from 'office-ui-fabric-react/lib/TextField';
|
||||
import Handlers from './Handlers';
|
||||
import styles from "../App/App.module.scss";
|
||||
import { Label } from "office-ui-fabric-react/lib/Label";
|
||||
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
|
||||
import AppContext from "../App/AppContext";
|
||||
import HandlerValues from "../../../../models/Handlers";
|
||||
import { HttpClientResponse } from "@microsoft/sp-http";
|
||||
import { MessageBarType } from "office-ui-fabric-react/lib/MessageBar";
|
||||
|
||||
const GetProvisioningTemplate = () => {
|
||||
const ctx = React.useContext(AppContext);
|
||||
const [handlers, setHandlers] = React.useState([]);
|
||||
const [webUrl, setWebUrl] = React.useState(ctx.webUrl);
|
||||
const [template, setTemplate] = React.useState("");
|
||||
const [handlerValues, setHandlerValues] = React.useState(HandlerValues);
|
||||
const isNotAdmin = !ctx.isGlobalAdmin || !ctx.isSiteOwner;
|
||||
|
||||
const resetHandlers = () => {
|
||||
const allHandlers = Object.keys(HandlerValues);
|
||||
let newValues = HandlerValues;
|
||||
allHandlers.map(value => newValues[value] = false);
|
||||
setHandlerValues(newValues);
|
||||
};
|
||||
|
||||
const onHandlerChange = (ev: React.FormEvent<HTMLElement>, isChecked: boolean) => {
|
||||
const handlerName = ev.currentTarget.getAttribute("name");
|
||||
|
||||
if (handlerName === "All") {
|
||||
if (isChecked) {
|
||||
setHandlers(["All"]);
|
||||
setHandlerValues({ ...handlerValues, [handlerName]: isChecked });
|
||||
}
|
||||
|
||||
else {
|
||||
resetHandlers();
|
||||
setHandlers([]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
let newHandlers = [...handlers];
|
||||
if (isChecked)
|
||||
newHandlers = newHandlers.concat(handlerName);
|
||||
else
|
||||
newHandlers = newHandlers.filter(h => h != handlerName);
|
||||
setHandlers(newHandlers);
|
||||
setHandlerValues({ ...handlerValues, [handlerName]: isChecked });
|
||||
}
|
||||
};
|
||||
|
||||
const getTemplate = async () => {
|
||||
ctx.toggleLoading(true);
|
||||
ctx.updateMessageBarSettings({
|
||||
message: "",
|
||||
type: MessageBarType.success,
|
||||
visible: false
|
||||
});
|
||||
const response: HttpClientResponse = await ctx.appService.GetProvisioningTemplate(webUrl, handlers.join(","));
|
||||
|
||||
const responseText = await response.text();
|
||||
if (response.status === 200)
|
||||
setTemplate(responseText);
|
||||
else
|
||||
ctx.updateMessageBarSettings({
|
||||
message: responseText,
|
||||
type: MessageBarType.error,
|
||||
visible: true
|
||||
});
|
||||
ctx.toggleLoading(false);
|
||||
};
|
||||
|
||||
const downloadTemplate = () => {
|
||||
if (template !== "") {
|
||||
const fileName = "PnPProvisioningTemplate.xml";
|
||||
const fileContent = new Blob([template], { type: 'text/plain' });
|
||||
const downloadTag = document.createElement("a");
|
||||
downloadTag.setAttribute("href", window.URL.createObjectURL(fileContent));
|
||||
downloadTag.setAttribute("download", fileName);
|
||||
downloadTag.dataset.downloadurl = ['text/plain', downloadTag.download, downloadTag.href].join(':');
|
||||
downloadTag.draggable = true;
|
||||
downloadTag.classList.add('dragour');
|
||||
downloadTag.click();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.pivotContainer}>
|
||||
<div hidden={ctx.isLoading || template.length > 1}>
|
||||
<TextField disabled={!ctx.isGlobalAdmin} label="Site URL" value={webUrl} onChanged={(value) => setWebUrl(value)} />
|
||||
<Label>Handlers</Label>
|
||||
<Handlers disabled={!isNotAdmin} values={handlerValues} onHandlerChange={onHandlerChange} />
|
||||
<div className={styles.topMargin}>
|
||||
<PrimaryButton text="Get Template" className={styles.provisioningButton} disabled={(handlers.length < 1 || webUrl.length < 1) || !isNotAdmin} onClick={getTemplate} />
|
||||
<DefaultButton text="Reset" disabled={!isNotAdmin} onClick={resetHandlers} className={styles.provisioningButton} />
|
||||
</div>
|
||||
</div>
|
||||
<div hidden={template.length < 1}>
|
||||
<TextField label="Template" value={template} multiline rows={20} />
|
||||
<div className={styles.topMargin}>
|
||||
<PrimaryButton text="Download" onClick={downloadTemplate} className={styles.provisioningButton} />
|
||||
<DefaultButton text="Back" onClick={() => setTemplate("")} className={styles.provisioningButton} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GetProvisioningTemplate;
|
|
@ -0,0 +1,36 @@
|
|||
import * as React from "react";
|
||||
import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox';
|
||||
import styles from "../App/App.module.scss";
|
||||
import { IHandlers } from "../../../../models/Handlers";
|
||||
|
||||
interface IHandlerProps {
|
||||
onHandlerChange: (ev: React.FormEvent<HTMLElement>, isChecked: boolean) => void;
|
||||
values: IHandlers;
|
||||
disabled:boolean;
|
||||
}
|
||||
|
||||
const Handlers: React.FC<IHandlerProps> = (props) => {
|
||||
const { values } = props;
|
||||
const allHandlers = Object.keys(values);
|
||||
const checkboxes = allHandlers.map((value) =>
|
||||
<Checkbox disabled={props.disabled} key={value} className={styles.handlerCheckbox} checked={values.All || values[value]} name={value} label={value} onChange={props.onHandlerChange} />
|
||||
);
|
||||
return (
|
||||
<div className="ms-Grid">
|
||||
<div className={styles.row}>
|
||||
<div className={styles.column}>
|
||||
{
|
||||
checkboxes.slice(0, 15)
|
||||
}
|
||||
</div>
|
||||
<div className={styles.column}>
|
||||
{
|
||||
checkboxes.slice(15, 30)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Handlers;
|
|
@ -0,0 +1,20 @@
|
|||
import * as React from "react";
|
||||
import styles from "../App/App.module.scss";
|
||||
const loadingImage: any = require("../Assets/loading_dots.gif");
|
||||
|
||||
interface ILoadingProps{
|
||||
hidden:boolean;
|
||||
}
|
||||
|
||||
const Loading: React.FC<ILoadingProps> = (props) => {
|
||||
return (
|
||||
<div hidden={props.hidden}>
|
||||
<h3>
|
||||
Please wait, this process takes a while...
|
||||
</h3>
|
||||
<img src={loadingImage} className={styles.loadingImage} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loading;
|
|
@ -0,0 +1,15 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Site provisioning manager",
|
||||
"BasicGroupName": "Azure functions configuration",
|
||||
"DescriptionFieldLabel": "Description Field",
|
||||
"ErrorMessageUserNotAdmin": "You are not Site Admin to manage provisioning templates.",
|
||||
"LoadingLabel": "Please wait, this process takes a while...",
|
||||
"SuccessMessage": "Provisioning template has been applied successfully.",
|
||||
"GetTemplateLabel":"Get Template",
|
||||
"ApplyTemplateLable":"Apply Template",
|
||||
"ClientIdFieldLabel":"Application Id/Endpoint",
|
||||
"GetProvisioningUrlFieldLabel": "Get provisioning function URL",
|
||||
"ApplyProvisioningUrlFieldLabel": "Apply Provisioning function URL"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
declare interface ISiteProvisioningManagerWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
ClientIdFieldLabel: string;
|
||||
ErrorMessageUserNotAdmin:string;
|
||||
LoadingLabel:string;
|
||||
SuccessMessage:string;
|
||||
GetTemplateLabel:string;
|
||||
ApplyTemplateLable:string;
|
||||
GetProvisioningUrlFieldLabel:string;
|
||||
ApplyProvisioningUrlFieldLabel:string;
|
||||
}
|
||||
|
||||
declare module 'SiteProvisioningManagerWebPartStrings' {
|
||||
const strings: ISiteProvisioningManagerWebPartStrings;
|
||||
export = strings;
|
||||
}
|
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-2.9/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"
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|