Switch to functional components and re-factor
Code clean up, no chnages to the UI.
This commit is contained in:
parent
71c62ef07d
commit
a62ac9e0e0
|
@ -50,6 +50,7 @@ react-sites-selected-admin | Fredrik Thorild [@fthorild](https://twitter.com/fth
|
||||||
Version|Date|Comments
|
Version|Date|Comments
|
||||||
-------|----|--------
|
-------|----|--------
|
||||||
1.0|February 19, 2021|Initial release
|
1.0|February 19, 2021|Initial release
|
||||||
|
1.1|March 8, 2021|Switch to functional components. Re-factor
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"solution": {
|
"solution": {
|
||||||
"name": "site-selected-mngr-wp-client-side-solution",
|
"name": "site-selected-mngr-wp-client-side-solution",
|
||||||
"id": "7fec6393-3d66-4b11-8d55-5f609edf2a7a",
|
"id": "7fec6393-3d66-4b11-8d55-5f609edf2a7a",
|
||||||
"version": "1.0.0.0",
|
"version": "1.1.0.0",
|
||||||
"includeClientSideAssets": true,
|
"includeClientSideAssets": true,
|
||||||
"isDomainIsolated": true,
|
"isDomainIsolated": true,
|
||||||
"developer": {
|
"developer": {
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
import { ServiceKey, ServiceScope } from '@microsoft/sp-core-library';
|
||||||
|
import { MSGraphClientFactory, MSGraphClient } from '@microsoft/sp-http';
|
||||||
|
import { IAzureApp, IPermission } from './components/IAppInterfaces';
|
||||||
|
import * as strings from 'SitesSelectedManagerWebPartStrings';
|
||||||
|
|
||||||
|
|
||||||
|
export interface IService {
|
||||||
|
getApps(apiPermission: string): Promise<IAzureApp[]>;
|
||||||
|
getPermissions(siteUrl: URL): Promise<IPermission[]>
|
||||||
|
addPermissions(siteUrl: URL, payload: IPermission): Promise<void>;
|
||||||
|
deletePermissions(siteUrl: URL, appId: string): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Service implements IService {
|
||||||
|
|
||||||
|
public static readonly serviceKey: ServiceKey<IService> =
|
||||||
|
ServiceKey.create<IService>('sites-selected-admin-app:IService', Service);
|
||||||
|
|
||||||
|
private _msGraphClientFactory: MSGraphClientFactory;
|
||||||
|
|
||||||
|
private _siteId: string;
|
||||||
|
public get siteId(): string {
|
||||||
|
return this._siteId;
|
||||||
|
}
|
||||||
|
public set siteId(v: string) {
|
||||||
|
this._siteId = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(serviceScope: ServiceScope) {
|
||||||
|
serviceScope.whenFinished(() => {
|
||||||
|
this._msGraphClientFactory = serviceScope.consume(MSGraphClientFactory.serviceKey);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSiteId(siteUrl: URL): Promise<void> {
|
||||||
|
return new Promise<void>(async (resolve, reject) => {
|
||||||
|
const client = await this._msGraphClientFactory.getClient();
|
||||||
|
client
|
||||||
|
.api(`sites/${siteUrl.hostname}:${siteUrl.pathname}`)
|
||||||
|
.version("v1.0")
|
||||||
|
.select("id")
|
||||||
|
.get((error, site: any, rawResponse?: any) => {
|
||||||
|
if (site) {
|
||||||
|
this.siteId = site.id;
|
||||||
|
resolve()
|
||||||
|
} else {
|
||||||
|
error ? reject(error) : reject(strings.ErrorUnknown)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPermissionId(appId: string, permssions: IPermission[]): string {
|
||||||
|
let result: string;
|
||||||
|
permssions.forEach(element => {
|
||||||
|
element.grantedToIdentities.forEach(el => {
|
||||||
|
if (el.application.id === appId)
|
||||||
|
result = element.id
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!result) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getApps(apiPermissionGuid: string): Promise<IAzureApp[]> {
|
||||||
|
return new Promise<IAzureApp[]>(async (resolve, reject) => {
|
||||||
|
const client = await this._msGraphClientFactory.getClient();
|
||||||
|
client
|
||||||
|
.api('applications')
|
||||||
|
.version("v1.0")
|
||||||
|
.select("id,appId,displayName,requiredResourceAccess")
|
||||||
|
.get((error, apps: any, rawResponse?: any) => {
|
||||||
|
if (apps) {
|
||||||
|
const appsWithSitesSelected = apps.value.filter((obj) => {
|
||||||
|
return obj.requiredResourceAccess.some(({ resourceAccess }) =>
|
||||||
|
resourceAccess.some(({ id }) => id === apiPermissionGuid))
|
||||||
|
});
|
||||||
|
resolve(appsWithSitesSelected as IAzureApp[]);
|
||||||
|
} else {
|
||||||
|
error ? reject(error) : reject(strings.ErrorUnknown)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPermissions(siteUrl: URL): Promise<IPermission[]> {
|
||||||
|
return new Promise<IPermission[]>(async (resolve, reject) => {
|
||||||
|
const client = await this._msGraphClientFactory.getClient();
|
||||||
|
await this.getSiteId(siteUrl);
|
||||||
|
client
|
||||||
|
.api(`sites/${this.siteId}/permissions`)
|
||||||
|
.version("v1.0")
|
||||||
|
.get((error, permissions: any, rawResponse?: any) => {
|
||||||
|
if (permissions) {
|
||||||
|
resolve(permissions.value);
|
||||||
|
} else {
|
||||||
|
error ? reject(error) : reject(strings.ErrorUnknown)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public addPermissions(siteUrl: URL, payload: IPermission): Promise<void> {
|
||||||
|
return new Promise<void>(async (resolve, reject) => {
|
||||||
|
const client = await this._msGraphClientFactory.getClient();
|
||||||
|
await this.getSiteId(siteUrl);
|
||||||
|
client
|
||||||
|
.api(`sites/${this.siteId}/permissions`)
|
||||||
|
.version("v1.0")
|
||||||
|
.post(payload, (error, response: any, rawResponse?: any) => {
|
||||||
|
if (error) {
|
||||||
|
error ? reject(error) : reject(strings.ErrorUnknown)
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public deletePermissions(siteUrl: URL, appId: string): Promise<void> {
|
||||||
|
return new Promise<void>(async (resolve, reject) => {
|
||||||
|
|
||||||
|
|
||||||
|
console.warn('Are we herrrrre');
|
||||||
|
|
||||||
|
const client = await this._msGraphClientFactory.getClient();
|
||||||
|
console.warn('Are we herrrrre1');
|
||||||
|
|
||||||
|
const permissions = await this.getPermissions(siteUrl);
|
||||||
|
console.warn('Are we herrrrre2');
|
||||||
|
console.warn('ahhhahha');
|
||||||
|
console.warn(appId);
|
||||||
|
|
||||||
|
const permissionId = this.getPermissionId(appId, permissions);
|
||||||
|
console.warn('Are we herrrrre3....');
|
||||||
|
console.warn(permissionId);
|
||||||
|
|
||||||
|
|
||||||
|
if (permissionId) {
|
||||||
|
client
|
||||||
|
.api(`sites/${this.siteId}/permissions/${permissionId}`)
|
||||||
|
.version("v1.0")
|
||||||
|
.delete((error, response: any, rawResponse?: any) => {
|
||||||
|
if (error) {
|
||||||
|
console.warn('asdasd');
|
||||||
|
|
||||||
|
error ? reject(error) : reject(strings.ErrorUnknown)
|
||||||
|
} else {
|
||||||
|
console.warn('ååååååååååååååå');
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject(`${strings.ErrorNoPermissionsFound} ${appId}`)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,19 +3,15 @@
|
||||||
"id": "a4411651-9a37-4956-8948-ec5b053da96e",
|
"id": "a4411651-9a37-4956-8948-ec5b053da96e",
|
||||||
"alias": "SitesSelectedManagerWebPart",
|
"alias": "SitesSelectedManagerWebPart",
|
||||||
"componentType": "WebPart",
|
"componentType": "WebPart",
|
||||||
// The "*" signifies that the version should be taken from the package.json
|
|
||||||
"version": "*",
|
"version": "*",
|
||||||
"manifestVersion": 2,
|
"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,
|
"requiresCustomScript": false,
|
||||||
"supportedHosts": [
|
"supportedHosts": [
|
||||||
"SharePointWebPart"
|
"SharePointWebPart"
|
||||||
],
|
],
|
||||||
"preconfiguredEntries": [
|
"preconfiguredEntries": [
|
||||||
{
|
{
|
||||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
|
||||||
"group": {
|
"group": {
|
||||||
"default": "Other"
|
"default": "Other"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,29 +1,25 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as ReactDom from 'react-dom';
|
import * as ReactDom from 'react-dom';
|
||||||
import { Version } from '@microsoft/sp-core-library';
|
|
||||||
import {
|
import {
|
||||||
IPropertyPaneConfiguration,
|
IPropertyPaneConfiguration,
|
||||||
PropertyPaneTextField,
|
PropertyPaneTextField,
|
||||||
PropertyPaneToggle
|
PropertyPaneToggle
|
||||||
} from '@microsoft/sp-property-pane';
|
} from '@microsoft/sp-property-pane';
|
||||||
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
import { BaseClientSideWebPart, WebPartContext } from '@microsoft/sp-webpart-base';
|
||||||
|
|
||||||
import * as strings from 'SitesSelectedManagerWebPartStrings';
|
import * as strings from 'SitesSelectedManagerWebPartStrings';
|
||||||
import SitesSelectedManager from './components/SitesSelectedManager';
|
import { App } from './components/App';
|
||||||
import { ISitesSelectedManagerProps } from './components/ISitesSelectedManagerProps';
|
|
||||||
|
|
||||||
export interface ISitesSelectedManagerWebPartProps {
|
export interface IAppProperties {
|
||||||
description: string;
|
description: string;
|
||||||
|
context: WebPartContext;
|
||||||
showAbout: boolean;
|
showAbout: boolean;
|
||||||
aadGuid: string;
|
aadGuid: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class SitesSelectedManagerWebPart extends BaseClientSideWebPart<ISitesSelectedManagerWebPartProps> {
|
export default class SitesSelectedManagerWebPart extends BaseClientSideWebPart<IAppProperties> {
|
||||||
|
|
||||||
public render(): void {
|
public render(): void {
|
||||||
const element: React.ReactElement<ISitesSelectedManagerProps> = React.createElement(
|
const element: React.ReactElement<IAppProperties> = React.createElement(
|
||||||
SitesSelectedManager,
|
App,
|
||||||
{
|
{
|
||||||
description: this.properties.description,
|
description: this.properties.description,
|
||||||
context: this.context,
|
context: this.context,
|
||||||
|
@ -38,10 +34,6 @@ export default class SitesSelectedManagerWebPart extends BaseClientSideWebPart<I
|
||||||
ReactDom.unmountComponentAtNode(this.domElement);
|
ReactDom.unmountComponentAtNode(this.domElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get dataVersion(): Version {
|
|
||||||
return Version.parse('1.0');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
return {
|
return {
|
||||||
pages: [
|
pages: [
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { IAzureApp } from './IAppInterfaces';
|
||||||
|
import { AppList } from './AppList';
|
||||||
|
import { Icon, MessageBar, MessageBarType, Pivot, PivotItem } from 'office-ui-fabric-react';
|
||||||
|
import { Spinner } from '@fluentui/react';
|
||||||
|
import { IService, Service } from '../Service';
|
||||||
|
import { IAppProperties } from '../SitesSelectedManagerWebPart';
|
||||||
|
import * as strings from 'SitesSelectedManagerWebPartStrings';
|
||||||
|
import { AppCheckPermissions } from './AppCheckPermissions';
|
||||||
|
|
||||||
|
interface IMessageBoxProps {
|
||||||
|
resetChoice?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAppState {
|
||||||
|
showMessage: boolean;
|
||||||
|
site: string;
|
||||||
|
messageBarType: number;
|
||||||
|
message: string;
|
||||||
|
permissionJson: string;
|
||||||
|
apps: IAzureApp[];
|
||||||
|
getPerm: boolean;
|
||||||
|
permission: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const App: React.FunctionComponent<IAppProperties> = (props) => {
|
||||||
|
const [state, setState] = React.useState<IAppState>(
|
||||||
|
{ showMessage: false, site: '', messageBarType: null, message: '', permissionJson: '', apps: null, getPerm: false, permission: '' }
|
||||||
|
);
|
||||||
|
const [service] = React.useState<IService>(props.context.serviceScope.consume(Service.serviceKey))
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setState({ ...state, showMessage: false });
|
||||||
|
|
||||||
|
service.getApps(props.aadGuid)
|
||||||
|
.then((_apps) => {
|
||||||
|
setState({ ...state, apps: _apps });
|
||||||
|
if (_apps.length === 0) {
|
||||||
|
setState({
|
||||||
|
...state,
|
||||||
|
message: strings.ErrorNoAppsFoundMessage,
|
||||||
|
messageBarType: MessageBarType.info,
|
||||||
|
showMessage: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
let _errorDetail = strings.ErrorGettingApps;
|
||||||
|
const _errorHint = strings.ErrorHintGettingApps;
|
||||||
|
if (error.statusCode) {
|
||||||
|
_errorDetail = `${strings.ErrorHttp} ${error.statusCode} - ${error.message}`
|
||||||
|
}
|
||||||
|
setState({
|
||||||
|
...state,
|
||||||
|
messageBarType: MessageBarType.error,
|
||||||
|
apps: [],
|
||||||
|
message: `${_errorDetail} ${_errorHint}`,
|
||||||
|
showMessage: true
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const showMessage = (type: MessageBarType, message: string, autoDismiss: boolean = false, error?: any): void => {
|
||||||
|
setState({ ...state, showMessage: true, messageBarType: type, message: message });
|
||||||
|
if (autoDismiss) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setState({ ...state, showMessage: false });
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetChoice = () => {
|
||||||
|
setState({ ...state, showMessage: false, message: '' });
|
||||||
|
};
|
||||||
|
|
||||||
|
const SitesSelectedMessageBox = (p: IMessageBoxProps) => (
|
||||||
|
<MessageBar
|
||||||
|
messageBarType={state.messageBarType}
|
||||||
|
isMultiline={true}
|
||||||
|
onDismiss={resetChoice}
|
||||||
|
dismissButtonAriaLabel={strings.Close}
|
||||||
|
>
|
||||||
|
{state.message}
|
||||||
|
|
||||||
|
</MessageBar>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (state.apps) {
|
||||||
|
return <div>
|
||||||
|
<h1>{props.description}</h1>
|
||||||
|
{state.showMessage ? <SitesSelectedMessageBox resetChoice={resetChoice} /> : React.Fragment}
|
||||||
|
<Pivot>
|
||||||
|
{props.showAbout ? <PivotItem
|
||||||
|
headerText={strings.HomeTabTitle}
|
||||||
|
headerButtonProps={{
|
||||||
|
'data-order': 1,
|
||||||
|
'data-title': strings.HomeTabTitle
|
||||||
|
}}
|
||||||
|
itemIcon="Home"
|
||||||
|
>
|
||||||
|
<h3>{strings.HomeTitleMain}</h3>
|
||||||
|
<ul>
|
||||||
|
<li><Icon iconName="SharepointAppIcon16" /> {strings.HomeBulletList}</li>
|
||||||
|
<li><Icon iconName="SharepointAppIcon16" /> {strings.HomeBulletAdd} </li>
|
||||||
|
<li><Icon iconName="SharepointAppIcon16" /> {strings.HomeBulletClear} </li>
|
||||||
|
<li><Icon iconName="Permissions" /> {strings.HomeBulletCheck} </li>
|
||||||
|
</ul>
|
||||||
|
<h3>{strings.HomeTitleFYI}</h3>
|
||||||
|
<p>
|
||||||
|
{strings.HomeFYI}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>{strings.HomeAccessTitle}</h3>
|
||||||
|
<p>{strings.HomeAccess}</p>
|
||||||
|
</PivotItem> : React.Fragment}
|
||||||
|
<PivotItem headerText={strings.AddTabTitle} itemIcon="SharepointAppIcon16">
|
||||||
|
<AppList {...{ applications: state.apps, wpContext: props.context, showMessage: showMessage }} />
|
||||||
|
</PivotItem>
|
||||||
|
<PivotItem headerText={strings.CheckTabTitle} itemIcon="Permissions">
|
||||||
|
<AppCheckPermissions {...{ wpContext: props.context, showMessage: showMessage }} />
|
||||||
|
</PivotItem>
|
||||||
|
</Pivot>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return <div>
|
||||||
|
<Spinner label={strings.WorkingOnIt} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
|
||||||
|
import { MessageBarType, Stack, TextField } from 'office-ui-fabric-react';
|
||||||
|
import styles from './SitesSelectedManager.module.scss';
|
||||||
|
import { IService, Service } from '../Service';
|
||||||
|
import * as strings from 'SitesSelectedManagerWebPartStrings';
|
||||||
|
import { WebPartContext } from '@microsoft/sp-webpart-base';
|
||||||
|
|
||||||
|
interface IAppCheckPermissionsProps {
|
||||||
|
wpContext: WebPartContext,
|
||||||
|
showMessage: (type: MessageBarType, message: string, autoDismiss: boolean, error?: any) => void;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAppCheckPermissionsState {
|
||||||
|
getPerm: boolean;
|
||||||
|
site?: string;
|
||||||
|
permissionJson: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AppCheckPermissions: React.FunctionComponent<IAppCheckPermissionsProps> = (props) => {
|
||||||
|
const [state, setState] = React.useState<IAppCheckPermissionsState>({ getPerm: false, permissionJson: '' });
|
||||||
|
const [service] = React.useState<IService>(props.wpContext.serviceScope.consume(Service.serviceKey))
|
||||||
|
const toggle = () => setState({ ...state, getPerm: !state.getPerm });
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (state.site) {
|
||||||
|
setState({ ...state, permissionJson: strings.LoadingMessage });
|
||||||
|
const url = new URL(state.site);
|
||||||
|
service.getPermissions(url)
|
||||||
|
.then((permissions) => {
|
||||||
|
setState({ ...state, permissionJson: JSON.stringify(permissions, undefined, 4) });
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
let errorDetail = strings.ErrorGeneric;
|
||||||
|
let errorHint = ''
|
||||||
|
if (error.statusCode) {
|
||||||
|
errorDetail = strings.ErrorHttp
|
||||||
|
errorHint = `${error.statusCode} - ${error.message} ${strings.ErrorHintUrlFormat}`
|
||||||
|
}
|
||||||
|
props.showMessage(MessageBarType.error, `${errorDetail} ${errorHint}`, false);
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}, [state.getPerm])
|
||||||
|
|
||||||
|
return <div><h3>{strings.PermCheckTitle}</h3>
|
||||||
|
<p><strong>{strings.Info}</strong> {strings.PermCheckHint}</p>
|
||||||
|
|
||||||
|
<Stack className={styles.checkPermUi}>
|
||||||
|
<TextField onChange={(e: any) => setState({ ...state, site: e.target.value })} label={strings.CheckSiteLabel}
|
||||||
|
placeholder={strings.CheckSitePlaceholder} />
|
||||||
|
<PrimaryButton text={strings.CheckButtonText} onClick={toggle} allowDisabledFocus />
|
||||||
|
<TextField value={state.permissionJson} label={strings.CheckTextAreaLabel} multiline autoAdjustHeight />
|
||||||
|
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
|
||||||
|
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
|
||||||
|
import { ChoiceGroup, IChoiceGroupOption } from 'office-ui-fabric-react/lib/ChoiceGroup';
|
||||||
|
import { MessageBarType, TextField } from 'office-ui-fabric-react';
|
||||||
|
import styles from './SitesSelectedManager.module.scss';
|
||||||
|
import { IAzureApp, IPermission } from './IAppInterfaces';
|
||||||
|
import { IService, Service } from '../Service';
|
||||||
|
import * as strings from 'SitesSelectedManagerWebPartStrings';
|
||||||
|
import { WebPartContext } from '@microsoft/sp-webpart-base';
|
||||||
|
|
||||||
|
interface IAppDialogProps {
|
||||||
|
isHidden: boolean;
|
||||||
|
hideDialog: (hide: boolean) => void;
|
||||||
|
wpContext: WebPartContext,
|
||||||
|
selectedApp: IAzureApp;
|
||||||
|
isDeleteMode: boolean;
|
||||||
|
showMessage: (type: MessageBarType, message: string, autoDismiss: boolean, error?: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAppDialogState {
|
||||||
|
site: string;
|
||||||
|
permission: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AppDialog: React.FunctionComponent<IAppDialogProps> = (props) => {
|
||||||
|
const [state, setState] = React.useState<IAppDialogState>(
|
||||||
|
{ site: '', permission: '' });
|
||||||
|
const [service] = React.useState<IService>(props.wpContext.serviceScope.consume(Service.serviceKey))
|
||||||
|
const [mode, setMode] = React.useState({ add: false, delete: false });
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (state.site) {
|
||||||
|
const url = new URL(state.site);
|
||||||
|
const payload: IPermission = {
|
||||||
|
roles: state.permission.split('-'),
|
||||||
|
grantedToIdentities: [{ application: props.selectedApp }]
|
||||||
|
}
|
||||||
|
console.warn(payload);
|
||||||
|
|
||||||
|
service.addPermissions(url, payload).then(() => {
|
||||||
|
props.showMessage(MessageBarType.success, strings.DialogAddSuccess, true);
|
||||||
|
}, (error) => {
|
||||||
|
props.showMessage(MessageBarType.error, strings.ErrorGeneric, false, error);
|
||||||
|
})
|
||||||
|
props.hideDialog(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
}, [mode.add])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (state.site) {
|
||||||
|
service.deletePermissions(new URL(state.site), props.selectedApp.id).then(() => {
|
||||||
|
props.showMessage(MessageBarType.success, strings.DialogRemoveSuccess, true);
|
||||||
|
}, (error: any) => {
|
||||||
|
props.showMessage(MessageBarType.error, strings.ErrorGeneric, false, error);
|
||||||
|
});
|
||||||
|
props.hideDialog(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [mode.delete])
|
||||||
|
|
||||||
|
const addDialogContentProps = {
|
||||||
|
type: DialogType.largeHeader,
|
||||||
|
title: strings.DialogAddTitle,
|
||||||
|
subText: strings.DialogAddSubTitle,
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteDialogContentProps = {
|
||||||
|
type: DialogType.largeHeader,
|
||||||
|
title: strings.DialogDelTitle,
|
||||||
|
subText: strings.DialogDelSubTitle,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog className={styles.sitesSelectedManager}
|
||||||
|
hidden={props.isHidden}
|
||||||
|
onDismiss={(() => { props.hideDialog(true) })}
|
||||||
|
dialogContentProps={props.isDeleteMode ? deleteDialogContentProps : addDialogContentProps}
|
||||||
|
modalProps={{ isBlocking: false, styles: { main: { maxWidth: 450 } }, }}
|
||||||
|
>
|
||||||
|
<TextField onChange={(e: any) => setState({ ...state, site: e.target.value })} label={strings.CheckSiteLabel}
|
||||||
|
placeholder={strings.CheckSitePlaceholder} />
|
||||||
|
|
||||||
|
<ChoiceGroup className={props.isDeleteMode ? styles.dialogHidden : styles.dialogShow} onChange={(ev: any, option: IChoiceGroupOption) => {
|
||||||
|
setState({ ...state, permission: option.key })
|
||||||
|
|
||||||
|
}} options={[
|
||||||
|
{
|
||||||
|
key: 'read',
|
||||||
|
text: strings.Read,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'write',
|
||||||
|
text: strings.Write,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'read-write',
|
||||||
|
text: strings.ReadWrite,
|
||||||
|
}
|
||||||
|
]} />
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<PrimaryButton
|
||||||
|
onClick={() => { props.isDeleteMode ? setMode({ ...mode, delete: !mode.delete }) : setMode({ ...mode, add: !mode.add }) }}
|
||||||
|
text={props.isDeleteMode ? strings.Remove : strings.Grant} />
|
||||||
|
<DefaultButton onClick={(() => props.hideDialog(true))} text={strings.Cancel} />
|
||||||
|
</DialogFooter>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,107 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { DetailsList, DetailsListLayoutMode, Selection, IColumn, CheckboxVisibility, SelectionMode } from 'office-ui-fabric-react/lib/DetailsList';
|
||||||
|
import { MarqueeSelection } from 'office-ui-fabric-react/lib/MarqueeSelection';
|
||||||
|
import { IAppListItem, IAzureApp } from './IAppInterfaces';
|
||||||
|
import { ICommandBarItemProps, CommandBar, MessageBarType } from 'office-ui-fabric-react';
|
||||||
|
import styles from './SitesSelectedManager.module.scss';
|
||||||
|
import { AppDialog } from './AppDialog';
|
||||||
|
import { IObjectWithKey } from '@uifabric/utilities';
|
||||||
|
import * as strings from 'SitesSelectedManagerWebPartStrings';
|
||||||
|
import { WebPartContext } from '@microsoft/sp-webpart-base';
|
||||||
|
|
||||||
|
interface IAppListProps {
|
||||||
|
applications: Array<IAzureApp>;
|
||||||
|
wpContext: WebPartContext;
|
||||||
|
showMessage: (type: MessageBarType, message: string, autoDismiss: boolean, error?: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAppListState {
|
||||||
|
items?: IAppListItem[];
|
||||||
|
selectionDetails?: IAzureApp;
|
||||||
|
menuItems?: ICommandBarItemProps[];
|
||||||
|
dialogHidden?: boolean,
|
||||||
|
isDeleteMode?: boolean;
|
||||||
|
commandBarDisabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AppList: React.FunctionComponent<IAppListProps> = (props) => {
|
||||||
|
const [state, setState] = React.useState<IAppListState>(
|
||||||
|
{ dialogHidden: true, isDeleteMode: false, commandBarDisabled: true, selectionDetails: {} as IAzureApp }
|
||||||
|
);
|
||||||
|
const [selectedApp, setSelectedApp] = React.useState<IObjectWithKey[]>();
|
||||||
|
const [selection] = React.useState<Selection>(new Selection({
|
||||||
|
onSelectionChanged: () => setSelectedApp(selection.getSelection()
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (selectedApp) {
|
||||||
|
if (selectedApp.length === 0) {
|
||||||
|
setState({ ...state, commandBarDisabled: true });
|
||||||
|
} else {
|
||||||
|
const app = selectedApp[0] as any;
|
||||||
|
setState({ ...state, selectionDetails: { id: app.value, displayName: app.name }, commandBarDisabled: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [selectedApp])
|
||||||
|
|
||||||
|
const hideDialog = (hide: boolean) => {
|
||||||
|
setState({ ...state, dialogHidden: hide });
|
||||||
|
};
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<div>
|
||||||
|
<CommandBar className={styles.commandBar}
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: 'newItem',
|
||||||
|
text: strings.ListCommandBarAdd,
|
||||||
|
iconProps: { iconName: 'CloudAdd' },
|
||||||
|
split: false,
|
||||||
|
onClick: () => { setState({ ...state, dialogHidden: false, isDeleteMode: false }) },
|
||||||
|
disabled: state.commandBarDisabled,
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'upload',
|
||||||
|
text: strings.ListCommandBarDelete,
|
||||||
|
iconProps: { iconName: 'BlockedSiteSolid12' },
|
||||||
|
split: false,
|
||||||
|
onClick: () => { setState({ ...state, dialogHidden: false, isDeleteMode: true }) },
|
||||||
|
disabled: state.commandBarDisabled,
|
||||||
|
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<MarqueeSelection className={styles.listMargin} selection={selection}>
|
||||||
|
<DetailsList
|
||||||
|
items={props.applications.map(app => ({
|
||||||
|
key: 0,
|
||||||
|
name: app.displayName,
|
||||||
|
value: app.appId
|
||||||
|
|
||||||
|
}))}
|
||||||
|
columns={[
|
||||||
|
{ key: 'column1', name: strings.ListColAppName, fieldName: 'name', minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
{ key: 'column2', name: strings.ListColAppId, fieldName: 'value', minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
]}
|
||||||
|
setKey="set"
|
||||||
|
layoutMode={DetailsListLayoutMode.justified}
|
||||||
|
selection={selection}
|
||||||
|
checkboxVisibility={CheckboxVisibility.onHover}
|
||||||
|
selectionMode={SelectionMode.single}
|
||||||
|
selectionPreservedOnEmptyClick={true}
|
||||||
|
/>
|
||||||
|
</MarqueeSelection>
|
||||||
|
<AppDialog {...
|
||||||
|
{
|
||||||
|
isHidden: state.dialogHidden,
|
||||||
|
hideDialog: hideDialog,
|
||||||
|
wpContext: props.wpContext,
|
||||||
|
selectedApp: state.selectionDetails,
|
||||||
|
isDeleteMode: state.isDeleteMode,
|
||||||
|
showMessage: props.showMessage
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
export interface IPermission {
|
||||||
|
id?: string;
|
||||||
|
grantedToIdentities: IPermissionIdentity[];
|
||||||
|
roles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPermissionIdentity {
|
||||||
|
application: IAzureApp
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISharePointSite {
|
||||||
|
displayName: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAzureApp {
|
||||||
|
id?: string; //objectId
|
||||||
|
appId?: string; //clientId
|
||||||
|
displayName: string;
|
||||||
|
requiredResourceAccess?: IRequiredResourceAccess[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRequiredResourceAccess {
|
||||||
|
resourceAppId: string;
|
||||||
|
resourceAccess: IResourceAccess[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IResourceAccess {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAppListItem {
|
||||||
|
key: number;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}
|
|
@ -1,74 +0,0 @@
|
||||||
import { ICommandBarItemProps } from "office-ui-fabric-react";
|
|
||||||
import { ISitesSelectedManagerProps } from "./ISitesSelectedManagerProps";
|
|
||||||
|
|
||||||
|
|
||||||
export interface IDialogProps {
|
|
||||||
isHidden: boolean;
|
|
||||||
hideDialog: (hide: boolean) => void;
|
|
||||||
webPartProperties: ISitesSelectedManagerProps,
|
|
||||||
selectedApp: string;
|
|
||||||
isDeleteMode: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAppListItem {
|
|
||||||
key: number;
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAppListState {
|
|
||||||
items?: IAppListItem[];
|
|
||||||
selectionDetails?: string;
|
|
||||||
menuItems?: ICommandBarItemProps[];
|
|
||||||
dialogHidden?: boolean,
|
|
||||||
isDeleteMode?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMessageBoxProps {
|
|
||||||
resetChoice?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISitePermissionList {
|
|
||||||
value: ISitesSelectedPermissionPayload[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISitesSelectedPermissionPayload {
|
|
||||||
roles?: string[];
|
|
||||||
grantedToIdentities?: IAADApplicationWrapper[];
|
|
||||||
id?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISelectedSitesListProps {
|
|
||||||
value: Array<IAADApplication>;
|
|
||||||
webpartProperties: ISitesSelectedManagerProps;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISPSite {
|
|
||||||
displayName: string;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAADApplicationList {
|
|
||||||
value: Array<IAADApplication>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAADApplication {
|
|
||||||
id: string;
|
|
||||||
appId?: string;
|
|
||||||
displayName: string;
|
|
||||||
requiredResourceAccess?: Array<IRequiredResourceAccess>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAADApplicationWrapper {
|
|
||||||
application: IAADApplication;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IRequiredResourceAccess {
|
|
||||||
resourceAppId: string;
|
|
||||||
resourceAccess: Array<IResourceAccess>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IResourceAccess {
|
|
||||||
id: string;
|
|
||||||
type: string;
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
import { WebPartContext } from '@microsoft/sp-webpart-base';
|
|
||||||
|
|
||||||
export interface ISitesSelectedManagerProps {
|
|
||||||
description: string;
|
|
||||||
context: WebPartContext;
|
|
||||||
showAbout: boolean;
|
|
||||||
aadGuid: string;
|
|
||||||
}
|
|
|
@ -1,168 +0,0 @@
|
||||||
import * as React from 'react';
|
|
||||||
import { IAADApplication, IAADApplicationList, IMessageBoxProps, ISPSite } from './ISitesSelectedAppInterfaces';
|
|
||||||
import { ISitesSelectedManagerProps } from './ISitesSelectedManagerProps';
|
|
||||||
import { SitesSelectedAppList } from './SitesSelectedList';
|
|
||||||
import { Icon, MessageBar, MessageBarType, Pivot, PivotItem, PrimaryButton, Stack, TextField } from 'office-ui-fabric-react';
|
|
||||||
import { Spinner } from '@fluentui/react';
|
|
||||||
import styles from './SitesSelectedManager.module.scss';
|
|
||||||
|
|
||||||
|
|
||||||
export const SitesSelectedApp: React.FunctionComponent<ISitesSelectedManagerProps> = (props) => {
|
|
||||||
const [appState, setAppState] = React.useState<Array<IAADApplication>>()
|
|
||||||
const [site, setSite] = React.useState("");
|
|
||||||
const [permString, setpermString] = React.useState("");
|
|
||||||
const [showMessage, setShowMessage] = React.useState(false);
|
|
||||||
const [messageBarType, setMessageBarType] = React.useState<MessageBarType>();
|
|
||||||
const [message, setMessage] = React.useState("");
|
|
||||||
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const fetchData = async () => {
|
|
||||||
setShowMessage(false);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const client = await props.context.msGraphClientFactory.getClient();
|
|
||||||
const aadApps: IAADApplicationList = await client
|
|
||||||
.api('applications')
|
|
||||||
.version("v1.0")
|
|
||||||
.select("id,appId,displayName,requiredResourceAccess")
|
|
||||||
.get();
|
|
||||||
const appsWithSitesSelected = aadApps.value.filter((obj) => {
|
|
||||||
return obj.requiredResourceAccess.some(({ resourceAccess }) =>
|
|
||||||
resourceAccess.some(({ id }) => id === props.aadGuid))
|
|
||||||
});
|
|
||||||
setAppState(appsWithSitesSelected);
|
|
||||||
|
|
||||||
if (appsWithSitesSelected.length === 0) {
|
|
||||||
setMessageBarType(MessageBarType.info)
|
|
||||||
setMessage(`We couldn't find any apps with [Sites.Selected] - Don't think that's right?
|
|
||||||
Then you might want to double check the guid in webpart settings - The default is 883ea226-0bf2-4a8f-9f9d-92c9162a727d`);
|
|
||||||
setShowMessage(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
setMessageBarType(MessageBarType.error)
|
|
||||||
if (error.statusCode) {
|
|
||||||
setMessage(`Http error occured ${error.statusCode} - ${error.message} - have you consented this web part in API management?`);
|
|
||||||
} else {
|
|
||||||
setMessage(`Unknown error occured getting your apps - have you consented this web part in API management?`);
|
|
||||||
}
|
|
||||||
setAppState([]);
|
|
||||||
setShowMessage(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetchData()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
|
|
||||||
const checkSitePermission = async () => {
|
|
||||||
setpermString("...loading - Getting site");
|
|
||||||
setShowMessage(false);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const url = new URL(site);
|
|
||||||
const client = await props.context.msGraphClientFactory.getClient();
|
|
||||||
const siteData: ISPSite = await client
|
|
||||||
.api(`sites/${url.hostname}:${url.pathname}`)
|
|
||||||
.version("v1.0")
|
|
||||||
.select("displayName,id,description")
|
|
||||||
.get();
|
|
||||||
setpermString("...loading - Got the site");
|
|
||||||
|
|
||||||
const perms = await client
|
|
||||||
.api(`sites/${siteData.id}/permissions`)
|
|
||||||
.version("v1.0")
|
|
||||||
.get()
|
|
||||||
|
|
||||||
setpermString(JSON.stringify(perms.value, undefined, 4))
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
setMessageBarType(MessageBarType.error)
|
|
||||||
if (error.statusCode) {
|
|
||||||
setMessage(`Http error occured ${error.statusCode} - ${error.message} - Check the format of your URL
|
|
||||||
Correct format below:
|
|
||||||
https://tenant.sharepoint.com/sites/thesite`);
|
|
||||||
} else {
|
|
||||||
setMessage(`Unknown error`);
|
|
||||||
}
|
|
||||||
setpermString("");
|
|
||||||
setShowMessage(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const SitesSelectedMessageBox = (p: IMessageBoxProps) => (
|
|
||||||
<MessageBar
|
|
||||||
messageBarType={messageBarType}
|
|
||||||
isMultiline={true}
|
|
||||||
onDismiss={p.resetChoice}
|
|
||||||
dismissButtonAriaLabel="Close"
|
|
||||||
>
|
|
||||||
{message}
|
|
||||||
|
|
||||||
</MessageBar>
|
|
||||||
);
|
|
||||||
const resetChoice = React.useCallback(() => setShowMessage(false), []);
|
|
||||||
|
|
||||||
if (appState) {
|
|
||||||
return <div>
|
|
||||||
<h1>{props.description}</h1>
|
|
||||||
<Pivot>
|
|
||||||
{props.showAbout ? <PivotItem
|
|
||||||
headerText="Home / About"
|
|
||||||
headerButtonProps={{
|
|
||||||
'data-order': 1,
|
|
||||||
'data-title': 'Home / About'
|
|
||||||
}}
|
|
||||||
itemIcon="Home"
|
|
||||||
>
|
|
||||||
<h3>What can this webpart do?</h3>
|
|
||||||
<ul>
|
|
||||||
<li><Icon iconName="SharepointAppIcon16" /> List Azure AD applications that have the Microsoft graph api scope [Sites.Selected]</li>
|
|
||||||
<li><Icon iconName="SharepointAppIcon16" /> Add SharePoint sites to the listed apps which will enable the app to interact with these sites via the graph api</li>
|
|
||||||
<li><Icon iconName="SharepointAppIcon16" /> Clear all SharePoint site permissions for the selected app</li>
|
|
||||||
|
|
||||||
<li><Icon iconName="Permissions" /> Check what app(s) that has been added to a specific SharePoint site</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
<h3>Good to know</h3>
|
|
||||||
<p>
|
|
||||||
Due to api- and other limitations it is "not possible" to list all sites that have an app with permissions via this concept.
|
|
||||||
Furthermore, when checking a site you will see that it has n apps with access but not what access (Read,Write or Read/Write)
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
<h3>User access</h3>
|
|
||||||
<p>
|
|
||||||
In order to grant access for an app, the user of this webpart has to be a Site Collection Administrator of the site.
|
|
||||||
</p>
|
|
||||||
</PivotItem> : React.Fragment}
|
|
||||||
<PivotItem headerText="Add/Remove sites to Apps" itemIcon="SharepointAppIcon16">
|
|
||||||
<SitesSelectedAppList {...{ value: appState, webpartProperties: props }} />
|
|
||||||
{showMessage ? <SitesSelectedMessageBox resetChoice={resetChoice} /> : React.Fragment}
|
|
||||||
</PivotItem>
|
|
||||||
<PivotItem headerText="Check app permissions on a site" itemIcon="Permissions">
|
|
||||||
|
|
||||||
<h3>Use the form below to check a sites permissions</h3>
|
|
||||||
<p><strong>Info!</strong> If the result box shows [] it means there is no permissions granted</p>
|
|
||||||
|
|
||||||
<Stack className={styles.checkPermUi}>
|
|
||||||
|
|
||||||
<TextField onChange={(e: any) => setSite(e.target.value)} label="SharePoint site"
|
|
||||||
placeholder="Please enter URL here" />
|
|
||||||
|
|
||||||
<PrimaryButton text="Check permission" onClick={checkSitePermission} allowDisabledFocus />
|
|
||||||
|
|
||||||
<TextField value={permString} label="(Raw) - Permission object for site" multiline autoAdjustHeight />
|
|
||||||
|
|
||||||
</Stack>
|
|
||||||
{showMessage ? <SitesSelectedMessageBox resetChoice={resetChoice} /> : React.Fragment}
|
|
||||||
|
|
||||||
</PivotItem>
|
|
||||||
</Pivot>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return <div>
|
|
||||||
<Spinner label="Working on it..." />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,213 +0,0 @@
|
||||||
import * as React from 'react';
|
|
||||||
import { Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
|
|
||||||
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
|
|
||||||
import { ChoiceGroup, IChoiceGroupOption } from 'office-ui-fabric-react/lib/ChoiceGroup';
|
|
||||||
import { MessageBar, MessageBarType, TextField } from 'office-ui-fabric-react';
|
|
||||||
import { ISitesSelectedManagerProps } from './ISitesSelectedManagerProps';
|
|
||||||
import styles from './SitesSelectedManager.module.scss';
|
|
||||||
import { IAADApplicationWrapper, IDialogProps, IMessageBoxProps, ISitePermissionList, ISitesSelectedPermissionPayload, ISPSite } from './ISitesSelectedAppInterfaces';
|
|
||||||
|
|
||||||
const options = [
|
|
||||||
{
|
|
||||||
key: 'read',
|
|
||||||
text: 'Read',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'write',
|
|
||||||
text: 'Write',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'read-write',
|
|
||||||
text: 'Read / Write',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const modelProps = {
|
|
||||||
isBlocking: false,
|
|
||||||
styles: { main: { maxWidth: 450 } },
|
|
||||||
};
|
|
||||||
const addDialogContentProps = {
|
|
||||||
type: DialogType.largeHeader,
|
|
||||||
title: 'Grant access to the selected app to a SharePoint site collection',
|
|
||||||
subText: 'Enter a SharePoint site collection URL into the text field and select the wanted access level',
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteDialogContentProps = {
|
|
||||||
type: DialogType.largeHeader,
|
|
||||||
title: 'Remove the access for the selected app to a SharePoint site collection',
|
|
||||||
subText: 'Enter a SharePoint site collection URL into the text field and click "remove" to remove the access',
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const SitesSelectedDialog: React.FunctionComponent<IDialogProps> = (props) => {
|
|
||||||
const [site, setSite] = React.useState("");
|
|
||||||
const [perm, setPerm] = React.useState("");
|
|
||||||
const [showMessage, setShowMessage] = React.useState(false);
|
|
||||||
const [messageBarType, setMessageBarType] = React.useState<MessageBarType>();
|
|
||||||
const [message, setMessage] = React.useState("");
|
|
||||||
|
|
||||||
const _addPermissionToSite = async () => {
|
|
||||||
setShowMessage(false);
|
|
||||||
try {
|
|
||||||
const url = new URL(site);
|
|
||||||
const client = await props.webPartProperties.context.msGraphClientFactory.getClient();
|
|
||||||
const siteData: ISPSite = await client
|
|
||||||
.api(`sites/${url.hostname}:${url.pathname}`)
|
|
||||||
.version("v1.0")
|
|
||||||
.select("displayName,id,description")
|
|
||||||
.get()
|
|
||||||
|
|
||||||
const app: IAADApplicationWrapper = { application: { displayName: props.selectedApp.split('|')[0], id: props.selectedApp.split('|')[1] } }
|
|
||||||
const pl: ISitesSelectedPermissionPayload = {
|
|
||||||
roles: perm.split('-'),
|
|
||||||
grantedToIdentities: [app]
|
|
||||||
}
|
|
||||||
|
|
||||||
await client
|
|
||||||
.api(`sites/${siteData.id}/permissions`)
|
|
||||||
.version("v1.0")
|
|
||||||
.post(pl)
|
|
||||||
|
|
||||||
_handleSuccess("Yay! - Permissions successfully added!");
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
_handleError(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const _deletePermissionToSite = async () => {
|
|
||||||
|
|
||||||
try {
|
|
||||||
setShowMessage(false);
|
|
||||||
const url = new URL(site);
|
|
||||||
const client = await props.webPartProperties.context.msGraphClientFactory.getClient();
|
|
||||||
const siteData: ISPSite = await client
|
|
||||||
.api(`sites/${url.hostname}:${url.pathname}`)
|
|
||||||
.version("v1.0")
|
|
||||||
.select("displayName,id,description")
|
|
||||||
.get()
|
|
||||||
const permList: ISitePermissionList = await client
|
|
||||||
.api(`sites/${siteData.id}/permissions`)
|
|
||||||
.version("v1.0")
|
|
||||||
.get()
|
|
||||||
const permissionIdToRemove = _getPermissionIdFromPayload(props.selectedApp.split('|')[1], permList);
|
|
||||||
|
|
||||||
if (permissionIdToRemove) {
|
|
||||||
await client
|
|
||||||
.api(`sites/${siteData.id}/permissions/${permissionIdToRemove}`)
|
|
||||||
.version("v1.0")
|
|
||||||
.delete()
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleSuccess("Yay! - Permissions successfully deleted!");
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
_handleError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const _handleError = (error: any) => {
|
|
||||||
setMessageBarType(MessageBarType.error)
|
|
||||||
if (error.statusCode) {
|
|
||||||
setMessage(`Http error occured ${error.statusCode} - ${error.message} - Check the format of your URL
|
|
||||||
Correct format below:
|
|
||||||
https://tenant.sharepoint.com/sites/thesite`);
|
|
||||||
} else {
|
|
||||||
setMessage(`Unknown error occured`);
|
|
||||||
}
|
|
||||||
setShowMessage(true);
|
|
||||||
props.hideDialog(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
const _handleSuccess = (message: string) => {
|
|
||||||
setMessageBarType(MessageBarType.success)
|
|
||||||
setMessage(message);
|
|
||||||
setShowMessage(true);
|
|
||||||
props.hideDialog(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
setShowMessage(false);
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
const _getPermissionIdFromPayload = (appId: string, payload: ISitePermissionList): string => {
|
|
||||||
let result: string;
|
|
||||||
payload.value.forEach(element => {
|
|
||||||
element.grantedToIdentities.forEach(el => {
|
|
||||||
console.warn(el.application.id === appId);
|
|
||||||
if (el.application.id === appId)
|
|
||||||
result = element.id
|
|
||||||
})
|
|
||||||
});
|
|
||||||
if (!result) {
|
|
||||||
throw new Error("App could not be found for site");
|
|
||||||
} else {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const _onChange = (ev: React.FormEvent<HTMLInputElement>, option: IChoiceGroupOption): void => {
|
|
||||||
setPerm(option.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
const SitesSelectedStatusMessage = (p: IMessageBoxProps) => (
|
|
||||||
<MessageBar
|
|
||||||
messageBarType={messageBarType}
|
|
||||||
isMultiline={true}
|
|
||||||
onDismiss={p.resetChoice}
|
|
||||||
dismissButtonAriaLabel="Close"
|
|
||||||
>
|
|
||||||
{message}
|
|
||||||
|
|
||||||
</MessageBar>
|
|
||||||
);
|
|
||||||
|
|
||||||
const resetChoice = React.useCallback(() => setShowMessage(false), []);
|
|
||||||
if (props.isDeleteMode) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
|
|
||||||
{showMessage ? <SitesSelectedStatusMessage resetChoice={resetChoice} /> : React.Fragment}
|
|
||||||
|
|
||||||
<Dialog className={styles.sitesSelectedManager}
|
|
||||||
hidden={props.isHidden}
|
|
||||||
onDismiss={(() => { props.hideDialog(true) })}
|
|
||||||
dialogContentProps={deleteDialogContentProps}
|
|
||||||
modalProps={modelProps}
|
|
||||||
|
|
||||||
>
|
|
||||||
|
|
||||||
<TextField onChange={(e: any) => setSite(e.target.value)} label="SharePoint site"
|
|
||||||
placeholder="Please enter URL here" />
|
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<PrimaryButton onClick={_deletePermissionToSite} text="Save" />
|
|
||||||
<DefaultButton onClick={(() => props.hideDialog(true))} text="Cancel" />
|
|
||||||
</DialogFooter>
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
|
|
||||||
{showMessage ? <SitesSelectedStatusMessage resetChoice={resetChoice} /> : React.Fragment}
|
|
||||||
|
|
||||||
<Dialog className={styles.sitesSelectedManager}
|
|
||||||
hidden={props.isHidden}
|
|
||||||
onDismiss={(() => { props.hideDialog(true) })}
|
|
||||||
dialogContentProps={addDialogContentProps}
|
|
||||||
modalProps={modelProps}
|
|
||||||
>
|
|
||||||
|
|
||||||
<TextField onChange={(e: any) => setSite(e.target.value)} label="SharePoint site"
|
|
||||||
placeholder="Please enter URL here" />
|
|
||||||
|
|
||||||
<ChoiceGroup onChange={_onChange} options={options} />
|
|
||||||
<DialogFooter>
|
|
||||||
<PrimaryButton onClick={_addPermissionToSite} text="Save" />
|
|
||||||
<DefaultButton onClick={(() => props.hideDialog(true))} text="Cancel" />
|
|
||||||
</DialogFooter>
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,142 +0,0 @@
|
||||||
import * as React from 'react';
|
|
||||||
import { DetailsList, DetailsListLayoutMode, Selection, IColumn, CheckboxVisibility, SelectionMode } from 'office-ui-fabric-react/lib/DetailsList';
|
|
||||||
import { MarqueeSelection } from 'office-ui-fabric-react/lib/MarqueeSelection';
|
|
||||||
import { Fabric } from 'office-ui-fabric-react/lib/Fabric';
|
|
||||||
import { IAppListItem, IAppListState, ISelectedSitesListProps } from './ISitesSelectedAppInterfaces';
|
|
||||||
import { ICommandBarItemProps, CommandBar, IButtonProps } from 'office-ui-fabric-react';
|
|
||||||
import styles from './SitesSelectedManager.module.scss';
|
|
||||||
import { SitesSelectedDialog } from './SitesSelectedDialog';
|
|
||||||
|
|
||||||
export class SitesSelectedAppList extends React.Component<ISelectedSitesListProps, IAppListState> {
|
|
||||||
private _selection: Selection;
|
|
||||||
private _allItems: IAppListItem[];
|
|
||||||
private _columns: IColumn[];
|
|
||||||
private _items: ICommandBarItemProps[];
|
|
||||||
private _overflowButtonProps: IButtonProps;
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this._hideDialog = this._hideDialog.bind(this);
|
|
||||||
this._items = this._getMenu(true);
|
|
||||||
this._selection = new Selection({
|
|
||||||
onSelectionChanged: () => this.setState({ selectionDetails: this._getSelectionDetails() }),
|
|
||||||
});
|
|
||||||
|
|
||||||
this._allItems = [];
|
|
||||||
let i = 0;
|
|
||||||
this.props.value.forEach(element => {
|
|
||||||
this._allItems.push({
|
|
||||||
key: i,
|
|
||||||
name: element.displayName,
|
|
||||||
value: element.appId,
|
|
||||||
});
|
|
||||||
i = i + 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
this._columns = [
|
|
||||||
{ key: 'column1', name: 'App Name', fieldName: 'name', minWidth: 100, maxWidth: 200, isResizable: true },
|
|
||||||
{ key: 'column2', name: 'Azure AD App Id', fieldName: 'value', minWidth: 100, maxWidth: 200, isResizable: true },
|
|
||||||
];
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
items: this._allItems,
|
|
||||||
selectionDetails: this._getSelectionDetails(),
|
|
||||||
menuItems: this._items,
|
|
||||||
dialogHidden: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private _hideDialog(hide: boolean) {
|
|
||||||
this.setState(
|
|
||||||
{ dialogHidden: hide }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
private _getSelectionDetails(): string {
|
|
||||||
const selectionCount = this._selection.getSelectedCount();
|
|
||||||
switch (selectionCount) {
|
|
||||||
case 0:
|
|
||||||
this.setState(
|
|
||||||
{
|
|
||||||
menuItems: this._getMenu(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return 'No items selected';
|
|
||||||
case 1:
|
|
||||||
this.setState(
|
|
||||||
{
|
|
||||||
menuItems: this._getMenu(false)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const result = this._selection.getSelection()[0] as IAppListItem;
|
|
||||||
return `${result.name}|${result.value}`;
|
|
||||||
default:
|
|
||||||
return `${selectionCount} items selected`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private _getMenu(disabled: boolean): ICommandBarItemProps[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
key: 'newItem',
|
|
||||||
text: 'Add app permissions',
|
|
||||||
iconProps: { iconName: 'CloudAdd' },
|
|
||||||
split: false,
|
|
||||||
ariaLabel: 'New',
|
|
||||||
onClick: () => { this.setState({ dialogHidden: false, isDeleteMode: false }) },
|
|
||||||
disabled: disabled,
|
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'upload',
|
|
||||||
text: 'Clear app permissions',
|
|
||||||
iconProps: { iconName: 'BlockedSiteSolid12' },
|
|
||||||
split: false,
|
|
||||||
onClick: () => { this.setState({ dialogHidden: false, isDeleteMode: true }) },
|
|
||||||
disabled: disabled,
|
|
||||||
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
|
||||||
const { items } = this.state;
|
|
||||||
return (
|
|
||||||
<Fabric>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<CommandBar className={styles.commandBar}
|
|
||||||
items={this.state.menuItems}
|
|
||||||
overflowButtonProps={this._overflowButtonProps}
|
|
||||||
ariaLabel="Use left and right arrow keys to navigate between commands"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<MarqueeSelection className={styles.listMargin} selection={this._selection}>
|
|
||||||
<DetailsList
|
|
||||||
items={items}
|
|
||||||
columns={this._columns}
|
|
||||||
setKey="set"
|
|
||||||
layoutMode={DetailsListLayoutMode.justified}
|
|
||||||
selection={this._selection}
|
|
||||||
checkboxVisibility={CheckboxVisibility.onHover}
|
|
||||||
selectionMode={SelectionMode.single}
|
|
||||||
selectionPreservedOnEmptyClick={true}
|
|
||||||
ariaLabelForSelectionColumn="Toggle selection"
|
|
||||||
ariaLabelForSelectAllCheckbox="Toggle selection for all items"
|
|
||||||
checkButtonAriaLabel="Row checkbox"
|
|
||||||
/>
|
|
||||||
</MarqueeSelection>
|
|
||||||
<div> </div>
|
|
||||||
<SitesSelectedDialog {...
|
|
||||||
{
|
|
||||||
isHidden: this.state.dialogHidden,
|
|
||||||
hideDialog: this._hideDialog,
|
|
||||||
webPartProperties: this.props.webpartProperties,
|
|
||||||
selectedApp: this.state.selectionDetails,
|
|
||||||
isDeleteMode: this.state.isDeleteMode
|
|
||||||
}} />
|
|
||||||
|
|
||||||
</Fabric >
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,6 +4,13 @@
|
||||||
margin-bottom: 50px;
|
margin-bottom: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialogHidden{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialogShow{
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.commandBar {
|
.commandBar {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
import * as React from 'react';
|
|
||||||
import styles from './SitesSelectedManager.module.scss';
|
|
||||||
import { ISitesSelectedManagerProps } from './ISitesSelectedManagerProps';
|
|
||||||
import { SitesSelectedApp } from './SitesSelectedApp';
|
|
||||||
|
|
||||||
export default class SitesSelectedManager extends React.Component<ISitesSelectedManagerProps, {}> {
|
|
||||||
public render(): React.ReactElement<ISitesSelectedManagerProps> {
|
|
||||||
return (
|
|
||||||
<div className={styles.sitesSelectedManager}>
|
|
||||||
<SitesSelectedApp {...this.props} />
|
|
||||||
<footer />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,70 @@
|
||||||
define([], function() {
|
define([], function () {
|
||||||
return {
|
return {
|
||||||
"DescriptionFieldLabel": "Webpart Title",
|
"DescriptionFieldLabel": "Webpart Title",
|
||||||
"ShowAboutFieldLabel":"Show Home / About Tab",
|
"ShowAboutFieldLabel": "Show Home / About Tab",
|
||||||
"AADGuidLabel":"Sites Selected Permission GUID"
|
"AADGuidLabel": "Sites Selected Permission GUID",
|
||||||
|
"ErrorNoAppsFoundMessage": `We couldn't find any apps with [Sites.Selected] - Don't think that's right?
|
||||||
|
Then you might want to double check the guid in webpart settings - The default is 883ea226-0bf2-4a8f-9f9d-92c9162a727d`,
|
||||||
|
"ErrorGettingApps": "Unknown error occured getting your apps",
|
||||||
|
"ErrorHintGettingApps": "- have you consented this web part in API management?",
|
||||||
|
"ErrorHttp": "Http error occured",
|
||||||
|
"ErrorGeneric": "Error occured",
|
||||||
|
"ErrorUnknown": "Error occured",
|
||||||
|
"ErrorNoPermissionsFound": "No permissions found for removal for app:",
|
||||||
|
|
||||||
|
"ErrorHintUrlFormat": `- Check the format of your URL
|
||||||
|
Correct format below:
|
||||||
|
https://tenant.sharepoint.com/sites/thesite`,
|
||||||
|
|
||||||
|
"HomeTabTitle": "Home / About",
|
||||||
|
"HomeTitleMain": "What can this webpart do?",
|
||||||
|
"HomeTitleFYI": "Good to know",
|
||||||
|
"HomeFYI": `Due to api- and other limitations it is "not possible" to list all sites that have an app with permissions via this concept.
|
||||||
|
Furthermore, when checking a site you will see that it has n apps with access but not what access (Read,Write or Read/Write)`,
|
||||||
|
"HomeAccessTitle": "User access",
|
||||||
|
"HomeAccess": "In order to grant access for an app, the user of this webpart has to be a Site Collection Administrator of the site.",
|
||||||
|
"HomeBulletList": "List Azure AD applications that have the Microsoft graph api scope [Sites.Selected]",
|
||||||
|
"HomeBulletAdd": "Add SharePoint sites to the listed apps which will enable the app to interact with these sites via the graph api",
|
||||||
|
"HomeBulletClear": "Clear all SharePoint site permissions for the selected app",
|
||||||
|
"HomeBulletCheck": "Check what app(s) that has been added to a specific SharePoint site",
|
||||||
|
|
||||||
|
"AddTabTitle": "Add/Remove sites to Apps",
|
||||||
|
|
||||||
|
"CheckTabTitle": "Check app permissions on a site",
|
||||||
|
"CheckSiteLabel": "SharePoint site",
|
||||||
|
"CheckSitePlaceholder": "Please enter URL here",
|
||||||
|
"CheckButtonText": "Check permission",
|
||||||
|
"CheckTextAreaLabel": "(Raw) - Permission object for site",
|
||||||
|
|
||||||
|
"DialogAddSuccess": "Yay! - Permissions successfully added!",
|
||||||
|
"DialogRemoveSuccess": "Yay! - Permissions successfully removed!",
|
||||||
|
|
||||||
|
"DialogAddTitle": "Grant access to the selected app to a SharePoint site collection",
|
||||||
|
"DialogAddSubTitle": "Enter a SharePoint site collection URL into the text field and select the wanted access level",
|
||||||
|
"DialogDelTitle": "Remove the access for the selected app to a SharePoint site collection",
|
||||||
|
"DialogDelSubTitle": "Enter a SharePoint site collection URL into the text field and click \"remove\" to remove the access",
|
||||||
|
|
||||||
|
"ListCommandBarAdd": "Add app permissions",
|
||||||
|
"ListCommandBarDelete": "Clear app permissions",
|
||||||
|
"ListColAppName": "App Name",
|
||||||
|
"ListColAppId": "Azure AD App Id",
|
||||||
|
|
||||||
|
"PermCheckTitle": "",
|
||||||
|
"PermCheckHint": "If the result box shows [] it means there is no permissions granted",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"LoadingMessage": "...loading",
|
||||||
|
"Close": "Close",
|
||||||
|
"WorkingOnIt": "Working on it...",
|
||||||
|
|
||||||
|
|
||||||
|
"Read": "Read",
|
||||||
|
"Write": "Write",
|
||||||
|
"ReadWrite": "Read / Write",
|
||||||
|
"Remove": "Remove",
|
||||||
|
"Grant": "Grant",
|
||||||
|
"Cancel": "Cancel",
|
||||||
|
"Info": "Info"
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -1,7 +1,62 @@
|
||||||
declare interface ISitesSelectedManagerWebPartStrings {
|
declare interface ISitesSelectedManagerWebPartStrings {
|
||||||
DescriptionFieldLabel: string;
|
DescriptionFieldLabel: string;
|
||||||
ShowAboutFieldLabel: string;
|
ShowAboutFieldLabel: string;
|
||||||
AADGuidLabel:string;
|
AADGuidLabel: string;
|
||||||
|
|
||||||
|
ErrorNoAppsFoundMessage: string;
|
||||||
|
ErrorGettingApps: string;
|
||||||
|
ErrorHintGettingApps: string;
|
||||||
|
ErrorHttp: string;
|
||||||
|
ErrorGeneric: string,
|
||||||
|
ErrorUnknown: string,
|
||||||
|
ErrorNoPermissionsFound: string,
|
||||||
|
ErrorHintUrlFormat: string,
|
||||||
|
|
||||||
|
HomeTabTitle: string;
|
||||||
|
HomeTitleMain: string;
|
||||||
|
HomeBulletList: string;
|
||||||
|
HomeBulletAdd: string;
|
||||||
|
HomeBulletClear: string;
|
||||||
|
HomeBulletCheck: string;
|
||||||
|
HomeTitleFYI: string;
|
||||||
|
HomeFYI: string;
|
||||||
|
HomeAccessTitle: string;
|
||||||
|
HomeAccess: string;
|
||||||
|
|
||||||
|
AddTabTitle: string;
|
||||||
|
|
||||||
|
CheckTabTitle: string;
|
||||||
|
CheckSiteLabel: string;
|
||||||
|
CheckSitePlaceholder: string;
|
||||||
|
CheckButtonText: string;
|
||||||
|
CheckTextAreaLabel: string;
|
||||||
|
|
||||||
|
DialogAddSuccess: string;
|
||||||
|
DialogRemoveSuccess: string;
|
||||||
|
DialogAddTitle: string
|
||||||
|
DialogAddSubTitle: string
|
||||||
|
DialogDelTitle: string
|
||||||
|
DialogDelSubTitle: string
|
||||||
|
|
||||||
|
ListCommandBarAdd: string;
|
||||||
|
ListCommandBarDelete: string;
|
||||||
|
ListColAppName: string;
|
||||||
|
ListColAppId: string;
|
||||||
|
|
||||||
|
PermCheckTitle: string;
|
||||||
|
PermCheckHint: string;
|
||||||
|
|
||||||
|
LoadingMessage: string;
|
||||||
|
Close: string;
|
||||||
|
WorkingOnIt: string;
|
||||||
|
|
||||||
|
Read: string;
|
||||||
|
Write: string;
|
||||||
|
ReadWrite: string;
|
||||||
|
Remove: string;
|
||||||
|
Grant: string;
|
||||||
|
Cancel: string;
|
||||||
|
Info: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'SitesSelectedManagerWebPartStrings' {
|
declare module 'SitesSelectedManagerWebPartStrings' {
|
||||||
|
|
Loading…
Reference in New Issue