Merge pull request #966 from LauraKokkarinen/PR951
Manual merge of PR951
|
@ -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
|
||||||
|
}
|
||||||
|
}
|