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
|
||||
-------|----|--------
|
||||
1.0|February 19, 2021|Initial release
|
||||
1.1|March 8, 2021|Switch to functional components. Re-factor
|
||||
|
||||
## Disclaimer
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"solution": {
|
||||
"name": "site-selected-mngr-wp-client-side-solution",
|
||||
"id": "7fec6393-3d66-4b11-8d55-5f609edf2a7a",
|
||||
"version": "1.0.0.0",
|
||||
"version": "1.1.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"isDomainIsolated": true,
|
||||
"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",
|
||||
"alias": "SitesSelectedManagerWebPart",
|
||||
"componentType": "WebPart",
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"requiresCustomScript": false,
|
||||
"supportedHosts": [
|
||||
"SharePointWebPart"
|
||||
],
|
||||
"preconfiguredEntries": [
|
||||
{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
|
||||
"group": {
|
||||
"default": "Other"
|
||||
},
|
||||
|
|
|
@ -1,29 +1,25 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField,
|
||||
PropertyPaneToggle
|
||||
} from '@microsoft/sp-property-pane';
|
||||
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||
|
||||
import { BaseClientSideWebPart, WebPartContext } from '@microsoft/sp-webpart-base';
|
||||
import * as strings from 'SitesSelectedManagerWebPartStrings';
|
||||
import SitesSelectedManager from './components/SitesSelectedManager';
|
||||
import { ISitesSelectedManagerProps } from './components/ISitesSelectedManagerProps';
|
||||
import { App } from './components/App';
|
||||
|
||||
export interface ISitesSelectedManagerWebPartProps {
|
||||
export interface IAppProperties {
|
||||
description: string;
|
||||
context: WebPartContext;
|
||||
showAbout: boolean;
|
||||
aadGuid: string;
|
||||
|
||||
}
|
||||
|
||||
export default class SitesSelectedManagerWebPart extends BaseClientSideWebPart<ISitesSelectedManagerWebPartProps> {
|
||||
|
||||
export default class SitesSelectedManagerWebPart extends BaseClientSideWebPart<IAppProperties> {
|
||||
public render(): void {
|
||||
const element: React.ReactElement<ISitesSelectedManagerProps> = React.createElement(
|
||||
SitesSelectedManager,
|
||||
const element: React.ReactElement<IAppProperties> = React.createElement(
|
||||
App,
|
||||
{
|
||||
description: this.properties.description,
|
||||
context: this.context,
|
||||
|
@ -38,10 +34,6 @@ export default class SitesSelectedManagerWebPart extends BaseClientSideWebPart<I
|
|||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
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;
|
||||
}
|
||||
|
||||
.dialogHidden{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dialogShow{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.commandBar {
|
||||
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 {
|
||||
"DescriptionFieldLabel": "Webpart Title",
|
||||
"ShowAboutFieldLabel":"Show Home / About Tab",
|
||||
"AADGuidLabel":"Sites Selected Permission GUID"
|
||||
"ShowAboutFieldLabel": "Show Home / About Tab",
|
||||
"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 {
|
||||
DescriptionFieldLabel: 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' {
|
||||
|
|
Loading…
Reference in New Issue