Skip to content

Commit 8d9f71f

Browse files
committed
sites selected permissions and new link in quick links.
1 parent a50bade commit 8d9f71f

File tree

5 files changed

+185
-3
lines changed

5 files changed

+185
-3
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sp-editor",
3-
"version": "7.8.2",
3+
"version": "7.8.3",
44
"private": true,
55
"homepage": ".",
66
"engines": {

public/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "SP Editor",
33
"homepage_url": "https://microsoftedge.microsoft.com/addons/detail/affnnhcbfmcbbdlcadgkdbfafigmjdkk",
4-
"version": "7.8.2",
4+
"version": "7.8.3",
55
"description": "Create and update SharePoint Online/SP2013/SP2016/SP2019 css/js files, inject files to web, manage web/list properties, list Webhook",
66
"manifest_version": 3,
77
"devtools_page": "devtools.html",

src/popup/Components/Actions.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from '@fluentui/react';
88
import ChangePageLayout from './ChangePageLayout';
99
import UpdateTranslations from './UpdateTranslations/UpdateTranslations';
10+
import SitesSelectedPermissions from './SitesSelectedPermissions';
1011

1112
export interface IQuickLinkListProps {
1213
ctx: any;
@@ -38,6 +39,7 @@ const Actions = ({ ctx, plo, tabId }: IQuickLinkListProps) => {
3839
</Separator>
3940
<ChangePageLayout ctx={ctx} plo={plo} tabId={tabId} />
4041
<UpdateTranslations ctx={ctx} plo={plo} tabId={tabId} />
42+
<SitesSelectedPermissions ctx={ctx} plo={plo} tabId={tabId} />
4143
</ScrollablePane>
4244
) : (
4345
<></>

src/popup/Components/QuickLinkList.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,17 @@ const QuickLinkList = ({ ctx, appCatalogUrl, tabUrl }: IQuickLinkListProps) => {
151151
disabled={!appCatalogUrl}
152152
url={appCatalogUrl ? appCatalogUrl + '/AppCatalog/Forms/AllItems.aspx' : ''}
153153
/>
154+
<QuickLinkButton
155+
text={'ACS Grant App'}
156+
iconName={'WebAppBuilderFragmentCreate'}
157+
disabled={!ctx || !ctx.isSPO || !ctx.portalUrl}
158+
url={
159+
ctx && ctx.isSPO && ctx.portalUrl
160+
? ctx.portalUrl.toLocaleLowerCase().replace('.sharepoint.', '-admin.sharepoint.') +
161+
'_layouts/15/appinv.aspx'
162+
: ''
163+
}
164+
/>
154165
<Separator alignContent="start" styles={separatorStyles}>
155166
Current site
156167
</Separator>
@@ -198,6 +209,12 @@ const QuickLinkList = ({ ctx, appCatalogUrl, tabUrl }: IQuickLinkListProps) => {
198209
disabled={!ctx || !ctx.webAbsoluteUrl}
199210
url={ctx && ctx.webAbsoluteUrl ? ctx.webAbsoluteUrl + '/_layouts/15/storman.aspx' : ''}
200211
/>
212+
<QuickLinkButton
213+
text={'ACS Grant App '}
214+
iconName={'WebAppBuilderFragmentCreate'}
215+
disabled={!ctx || !ctx.webAbsoluteUrl}
216+
url={ctx && ctx.webAbsoluteUrl ? ctx.webAbsoluteUrl + '/_layouts/15/appinv.aspx' : ''}
217+
/>
201218
<Separator alignContent="start" styles={separatorStyles}>
202219
Current user
203220
</Separator>
@@ -260,4 +277,4 @@ const QuickLinkList = ({ ctx, appCatalogUrl, tabUrl }: IQuickLinkListProps) => {
260277
);
261278
};
262279

263-
export default QuickLinkList;
280+
export default QuickLinkList;
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import { CommandBarButton, DefaultButton, Dialog, DialogFooter, DialogType, MessageBar, MessageBarType, Stack, Text } from '@fluentui/react';
2+
import { useEffect, useState } from 'react';
3+
import { IQuickLinkListProps } from './Actions';
4+
import { buttonStyles } from './QuickLinkButton';
5+
6+
function fetchSitePermissions(hostname: string, siteId: string, webId: string) {
7+
const apiUrl = `https://${hostname}/_api/v2.1/sites/${hostname},${siteId},${webId}/permissions`;
8+
9+
return fetch(apiUrl, {
10+
method: 'GET',
11+
headers: {
12+
'accept': 'application/json',
13+
'X-ClientService-ClientTag': 'SPEDITOR'
14+
},
15+
credentials: 'include'
16+
})
17+
.then(response => {
18+
if (!response.ok) {
19+
throw new Error(`Failed to fetch permissions: ${response.status} ${response.statusText}`);
20+
}
21+
return response.json();
22+
})
23+
.then(data => data.value || [])
24+
.catch(err => {
25+
throw err;
26+
});
27+
}
28+
29+
const SitesSelectedPermissions = ({ plo, tabId, ctx }: IQuickLinkListProps) => {
30+
31+
const modelProps = {
32+
isBlocking: false,
33+
styles: { main: { maxWidth: 600 } },
34+
};
35+
const dialogContentProps = {
36+
type: DialogType.largeHeader,
37+
title: 'Site Selected Permissions',
38+
subText: "Applications with permissions to access this site.",
39+
};
40+
41+
const [hideDialog, setHideDialog] = useState(true);
42+
const [permissions, setPermissions] = useState<any[]>([]);
43+
const [loading, setLoading] = useState(false);
44+
const [error, setError] = useState<string | null>(null);
45+
46+
useEffect(() => {
47+
if (!hideDialog && ctx) {
48+
loadPermissions();
49+
}
50+
}, [hideDialog, ctx]);
51+
52+
const loadPermissions = async () => {
53+
if (!ctx?.siteId || !ctx?.webId) {
54+
setError('Site ID or Web ID not available');
55+
return;
56+
}
57+
58+
if (!ctx?.webAbsoluteUrl) {
59+
setError('Web absolute URL not available');
60+
return;
61+
}
62+
63+
setLoading(true);
64+
setError(null);
65+
66+
try {
67+
// Extract hostname from context
68+
const hostname = new URL(ctx.webAbsoluteUrl).hostname;
69+
70+
if (!hostname) {
71+
throw new Error('Failed to extract hostname from URL');
72+
}
73+
74+
// Execute the fetch in the page context
75+
const results = await chrome.scripting.executeScript({
76+
target: { tabId: tabId },
77+
world: 'MAIN',
78+
args: [hostname, ctx.siteId, ctx.webId],
79+
func: fetchSitePermissions,
80+
});
81+
82+
if (results && results[0] && results[0].result) {
83+
setPermissions(results[0].result);
84+
} else {
85+
setPermissions([]);
86+
}
87+
} catch (err) {
88+
setError(err instanceof Error ? err.message : 'Failed to fetch permissions');
89+
console.error('Error fetching site permissions:', err);
90+
} finally {
91+
setLoading(false);
92+
}
93+
};
94+
95+
return (<>
96+
<CommandBarButton
97+
text={'Select Sites Permissions'}
98+
iconProps={{ iconName: 'Permissions' }}
99+
styles={buttonStyles}
100+
onClick={() => setHideDialog(false)}
101+
/>
102+
<Dialog
103+
hidden={hideDialog}
104+
onDismiss={() => setHideDialog(true)}
105+
dialogContentProps={dialogContentProps}
106+
modalProps={modelProps}>
107+
108+
{loading && <MessageBar messageBarType={MessageBarType.info}>Loading permissions...</MessageBar>}
109+
110+
{error && <MessageBar messageBarType={MessageBarType.error}>{error}</MessageBar>}
111+
112+
{!loading && !error && permissions.length === 0 && (
113+
<MessageBar messageBarType={MessageBarType.warning}>No permissions found</MessageBar>
114+
)}
115+
116+
{!loading && permissions.length > 0 && (
117+
<Stack tokens={{ childrenGap: 12 }} styles={{ root: { marginTop: 16, maxHeight: '400px', overflowY: 'auto' } }}>
118+
{permissions.map((permission, index) => {
119+
const app = permission.grantedToIdentities?.[0]?.application;
120+
return (
121+
<Stack key={index}>
122+
<Stack tokens={{ childrenGap: 8 }}>
123+
<Text variant="medium" styles={{ root: { fontWeight: 600, color: '#323130' } }}>
124+
{app?.displayName || 'Unknown Application'}
125+
</Text>
126+
<Stack tokens={{ childrenGap: 4 }}>
127+
<Text variant="small" styles={{ root: { color: '#605e5c', fontWeight: 600 } }}>
128+
Application ID:
129+
</Text>
130+
<Text variant="small" styles={{ root: { color: '#323130', fontFamily: 'monospace', fontSize: '11px' } }}>
131+
{app?.id || 'N/A'}
132+
</Text>
133+
</Stack>
134+
<Stack tokens={{ childrenGap: 4 }}>
135+
<Text variant="small" styles={{ root: { color: '#605e5c', fontWeight: 600 } }}>
136+
Roles:
137+
</Text>
138+
<Text variant="small" styles={{ root: { color: '#323130' } }}>
139+
{permission.roles?.join(', ') || 'No roles assigned'}
140+
</Text>
141+
</Stack>
142+
</Stack>
143+
{index < permissions.length - 1 && (
144+
<div style={{
145+
borderBottom: '1px solid #edebe9',
146+
marginTop: '12px'
147+
}} />
148+
)}
149+
</Stack>
150+
);
151+
})}
152+
</Stack>
153+
)}
154+
155+
<DialogFooter>
156+
<DefaultButton onClick={() => setHideDialog(true)} text="Close" />
157+
</DialogFooter>
158+
</Dialog>
159+
</>
160+
)
161+
}
162+
163+
export default SitesSelectedPermissions;

0 commit comments

Comments
 (0)