diff --git a/flows/GetAdminsOwners/Microsoft.Flow/flows/b0890fb0-8376-435a-bd78-094a5bc5fa4a/apisMap.json b/flows/GetAdminsOwners/Microsoft.Flow/flows/b0890fb0-8376-435a-bd78-094a5bc5fa4a/apisMap.json new file mode 100644 index 0000000..10f2430 --- /dev/null +++ b/flows/GetAdminsOwners/Microsoft.Flow/flows/b0890fb0-8376-435a-bd78-094a5bc5fa4a/apisMap.json @@ -0,0 +1 @@ +{"shared_sharepointonline":"ff855bae-28f2-4a92-b639-8967beb6f93d"} \ No newline at end of file diff --git a/flows/GetAdminsOwners/Microsoft.Flow/flows/b0890fb0-8376-435a-bd78-094a5bc5fa4a/connectionsMap.json b/flows/GetAdminsOwners/Microsoft.Flow/flows/b0890fb0-8376-435a-bd78-094a5bc5fa4a/connectionsMap.json new file mode 100644 index 0000000..b0ff90a --- /dev/null +++ b/flows/GetAdminsOwners/Microsoft.Flow/flows/b0890fb0-8376-435a-bd78-094a5bc5fa4a/connectionsMap.json @@ -0,0 +1 @@ +{"shared_sharepointonline":"44c279dc-36b3-4935-b191-14e78df6fc2c"} \ No newline at end of file diff --git a/flows/GetAdminsOwners/Microsoft.Flow/flows/b0890fb0-8376-435a-bd78-094a5bc5fa4a/definition.json b/flows/GetAdminsOwners/Microsoft.Flow/flows/b0890fb0-8376-435a-bd78-094a5bc5fa4a/definition.json new file mode 100644 index 0000000..1e88a7b --- /dev/null +++ b/flows/GetAdminsOwners/Microsoft.Flow/flows/b0890fb0-8376-435a-bd78-094a5bc5fa4a/definition.json @@ -0,0 +1 @@ +{"name":"43128f15-3c9c-4169-9034-98832d0c77a7","id":"/providers/Microsoft.Flow/flows/43128f15-3c9c-4169-9034-98832d0c77a7","type":"Microsoft.Flow/flows","properties":{"apiId":"/providers/Microsoft.PowerApps/apis/shared_logicflows","displayName":"Site Admin Lookup","definition":{"metadata":{"workflowEntityId":null,"processAdvisorMetadata":null,"flowChargedByPaygo":null,"flowclientsuspensionreason":"None","flowclientsuspensiontime":null,"flowclientsuspensionreasondetails":null,"creator":{"id":"4af50d87-e728-484e-874c-3f4536b38fee","type":"User","tenantId":"aecb54e0-7430-409d-86a4-7639c743eee7"},"provisioningMethod":"FromDefinition","failureAlertSubscription":true,"clientLastModifiedTime":"2026-08-25T20:18:46.4747484Z","lastModifiedBy":"4af50d87-e728-484e-874c-3f4536b38fee","connectionKeySavedTimeKey":"2026-08-25T20:18:46.4747484Z","creationSource":"Portal","modifiedSources":"Portal"},"$schema":"https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#","contentVersion":"undefined","parameters":{"$authentication":{"defaultValue":{},"type":"SecureObject"},"$connections":{"defaultValue":{},"type":"Object"}},"triggers":{"manual":{"metadata":{},"type":"Request","kind":"Http","inputs":{"schema":{"url":""},"triggerAuthenticationType":"All"}}},"actions":{"Get_the_site_admins":{"runAfter":{"admins":["Succeeded"]},"type":"OpenApiConnection","inputs":{"parameters":{"dataset":"@variables('siteUrl')","parameters/method":"GET","parameters/uri":"_api/web/SiteUserInfoList/Items?$filter=IsSiteAdmin eq 1"},"host":{"apiId":"/providers/Microsoft.PowerApps/apis/shared_sharepointonline","connectionName":"shared_sharepointonline","operationId":"HttpRequest"},"authentication":"@parameters('$authentication')"}},"Respond_to_a_Power_App_or_flow":{"runAfter":{"Loop_through_admins":["Succeeded"],"Loop_through_owners":["Succeeded"]},"type":"Response","kind":"PowerApp","inputs":{"schema":{"type":"object","properties":{"admins":{"title":"admins","type":"string","x-ms-content-hint":"TEXT","x-ms-dynamically-added":true},"owners":{"title":"owners","type":"string","x-ms-content-hint":"TEXT","x-ms-dynamically-added":true}},"additionalProperties":{}},"statusCode":200,"body":{"admins":"@{variables('admins')}","owners":"@{variables('owners')}"}}},"admins":{"runAfter":{"siteUrl":["Succeeded"]},"type":"InitializeVariable","inputs":{"variables":[{"name":"admins","type":"array"}]}},"Loop_through_admins":{"foreach":"@body('Get_the_site_admins')?['d']?['results']","actions":{"Add_the_site_admin":{"type":"AppendToArrayVariable","inputs":{"name":"admins","value":{"name":"@items('Loop_through_admins')?['Title']","email":"@items('Loop_through_admins')?['EMail']","type":"@items('Loop_through_admins')?['SPSUserType']"}}}},"runAfter":{"Get_the_site_admins":["Succeeded"]},"type":"Foreach"},"owners":{"runAfter":{"siteUrl":["Succeeded"]},"type":"InitializeVariable","inputs":{"variables":[{"name":"owners","type":"array"}]}},"Get_the_site_owners_group":{"runAfter":{"owners":["Succeeded"]},"type":"OpenApiConnection","inputs":{"parameters":{"dataset":"@variables('siteUrl')","parameters/method":"GET","parameters/uri":"_api/web/AssociatedOwnerGroup/Users"},"host":{"apiId":"/providers/Microsoft.PowerApps/apis/shared_sharepointonline","connectionName":"shared_sharepointonline","operationId":"HttpRequest"},"authentication":"@parameters('$authentication')"}},"Loop_through_owners":{"foreach":"@body('Get_the_site_owners_group')['d']['results']","actions":{"Add_the_owner":{"type":"AppendToArrayVariable","inputs":{"name":"owners","value":{"name":"@items('Loop_through_owners')?['Title']","email":"@items('Loop_through_owners')?['Email']","type":"@items('Loop_through_owners')?['PrincipalType']"}}}},"runAfter":{"Get_the_site_owners_group":["Succeeded"]},"type":"Foreach"},"siteUrl":{"runAfter":{},"type":"InitializeVariable","inputs":{"variables":[{"name":"siteUrl","type":"string","value":"@triggerBody()['url']"}]}}}},"connectionReferences":{"shared_sharepointonline":{"connectionName":"shared-sharepointonl-66a6d844-5e1b-4fa5-a8f1-40d37f28a005","source":"Embedded","id":"/providers/Microsoft.PowerApps/apis/shared_sharepointonline","tier":"NotSpecified","apiName":"sharepointonline","isProcessSimpleApiReferenceConversionAlreadyDone":false}},"flowFailureAlertSubscribed":false,"isManaged":false}} \ No newline at end of file diff --git a/flows/GetAdminsOwners/Microsoft.Flow/flows/manifest.json b/flows/GetAdminsOwners/Microsoft.Flow/flows/manifest.json new file mode 100644 index 0000000..de24c6b --- /dev/null +++ b/flows/GetAdminsOwners/Microsoft.Flow/flows/manifest.json @@ -0,0 +1 @@ +{"packageSchemaVersion":"1.0","flowAssets":{"assetPaths":["b0890fb0-8376-435a-bd78-094a5bc5fa4a"]}} \ No newline at end of file diff --git a/flows/GetAdminsOwners/manifest.json b/flows/GetAdminsOwners/manifest.json new file mode 100644 index 0000000..b7ff233 --- /dev/null +++ b/flows/GetAdminsOwners/manifest.json @@ -0,0 +1 @@ +{"schema":"1.0","details":{"displayName":"Site Admin Tool - Get Admins/Owners","description":"Flow to return the site admins and owners of a site.","createdTime":"2026-08-25T21:14:03.9176328Z","packageTelemetryId":"744faeef-8171-4ff9-91bf-a4fb3a96c35b","creator":"N/A","sourceEnvironment":"Commercial"},"resources":{"b0890fb0-8376-435a-bd78-094a5bc5fa4a":{"type":"Microsoft.Flow/flows","suggestedCreationType":"New","creationType":"Existing, New, Update","details":{"displayName":"Site Admin Lookup"},"configurableBy":"User","hierarchy":"Root","dependsOn":["ff855bae-28f2-4a92-b639-8967beb6f93d","44c279dc-36b3-4935-b191-14e78df6fc2c"]},"ff855bae-28f2-4a92-b639-8967beb6f93d":{"id":"/providers/Microsoft.PowerApps/apis/shared_sharepointonline","name":"shared_sharepointonline","type":"Microsoft.PowerApps/apis","suggestedCreationType":"Existing","details":{"displayName":"SharePoint","iconUri":"https://static.powerapps.com/resource/ppcr/releases/v1.0.1813/1.0.1813.4769/sharepointonline/icon.png"},"configurableBy":"System","hierarchy":"Child","dependsOn":[]},"44c279dc-36b3-4935-b191-14e78df6fc2c":{"type":"Microsoft.PowerApps/apis/connections","suggestedCreationType":"Existing","creationType":"Existing","details":{"displayName":"gunjan@dattaspo.onmicrosoft.com","iconUri":"https://static.powerapps.com/resource/ppcr/releases/v1.0.1813/1.0.1813.4769/sharepointonline/icon.png"},"configurableBy":"User","hierarchy":"Child","dependsOn":["ff855bae-28f2-4a92-b639-8967beb6f93d"]}}} \ No newline at end of file diff --git a/package.json b/package.json index 81b236e..c9b86db 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "adm-zip": "^0.5.18", "datatables.net": "^3.0.0", "datatables.net-bs5": "^3.0.0", - "dattatable": "^2.11.87", + "dattatable": "^2.11.88", "exceljs": "^4.4.0", "fast-xml-parser": "^5.9.3", "gd-sprest-bs": "^10.15.92", diff --git a/spfx/src/webparts/siteAdmin/SiteAdminWebPart.ts b/spfx/src/webparts/siteAdmin/SiteAdminWebPart.ts index be15a8a..05b5c90 100644 --- a/spfx/src/webparts/siteAdmin/SiteAdminWebPart.ts +++ b/spfx/src/webparts/siteAdmin/SiteAdminWebPart.ts @@ -71,12 +71,12 @@ export interface ISiteAdminWebPartProps { AppTitle: string; AuditOnly: boolean; DisableSensitivityLabelOverride: boolean; + FlowGetSiteAdminsAndOwners: string; HideAppPermissionsTab: boolean; HideAuditToolsTab: boolean; HideFeaturesTab: boolean; HideListsTab: boolean; HideCreateSiteBtn: boolean; - HideLoadAdminOwnerBtn: boolean; HideLoadOneDriveBtn: boolean; HideManagementTab: boolean; HideSearchTab: boolean; @@ -212,8 +212,8 @@ declare const SiteAdmin: { el: HTMLElement; title?: string; disableSensitivityLabelOverride?: boolean; + flowGetSiteAdminsAndOwners?: string; hideCreateSiteBtn?: boolean; - hideLoadAdminOwnerBtn: boolean; hideLoadOneDriveBtn: boolean; hideReports: { dlp: boolean; @@ -481,12 +481,6 @@ export default class SiteAdminWebPart extends BaseClientSideWebPart { + this._props.flowGetSiteAdminsAndOwners, this._props.hideLoadOneDriveBtn, () => { // Render the tabs new App(this._props, false); }, () => { @@ -154,7 +154,7 @@ export class App { text: "Load Site", onClick: () => { // Show the load form - LoadForm.showModal(this._props.hideLoadAdminOwnerBtn, this._props.hideLoadOneDriveBtn, () => { + LoadForm.showModal(this._props.flowGetSiteAdminsAndOwners, this._props.hideLoadOneDriveBtn, () => { // Render the tabs this.renderTabs(elRow.children[1] as HTMLElement, false); }, () => { diff --git a/src/ds.ts b/src/ds.ts index 9763d99..c3508e1 100644 --- a/src/ds.ts +++ b/src/ds.ts @@ -433,51 +433,37 @@ export class DataSource { } // Loads the admins/owners of the site - static loadAdminsOwners(siteUrl: string): PromiseLike<{ admins: IUserInfo[], owners: IUserInfo[] }> { + static loadAdminsOwners(flowUri: string, siteUrl: string): PromiseLike<{ admins: IUserInfo[], owners: IUserInfo[] }> { // Return a promise return new Promise((resolve, reject) => { - let errorFl = false; - let web = Web(siteUrl); - - // Get the admins - let admins: IUserInfo[] = []; - web.SiteUserInfoList().Items().query({ - Filter: "IsSiteAdmin eq 1" - }).execute(users => { - // Add the admins - users.results.forEach(user => { - admins.push({ - email: user["EMail"] || user["UserName"], - name: user.Title, - type: "Site Admin" - }); - }); - }, () => { - // Set the flag - errorFl = true; - }); - - // Get the owners - let owners: IUserInfo[] = []; - web.AssociatedOwnerGroup().Users().execute(users => { - // Add the owners - users.results.forEach(user => { - owners.push({ - email: user.Email || user.LoginName, - name: user.Title, - type: "Site Owner" - }); - }); - }, () => { - // Set the flag - errorFl = true; - }, true); - - // Wait for the requests to complete - web.done(() => { - // Complete the request - errorFl ? reject() : resolve({ admins, owners }) - }); + // Call the flow + fetch(flowUri, { + method: "POST", + headers: { + "Accept": "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ url: siteUrl }) + }) + .then(resp => { + // Ensure it was successful + if (resp.ok) { + return resp.json(); + } else { + throw new Error("Failed to load admins/owners"); + } + }) + .then(resp => { + // Ensure the data is converted + if (typeof (resp.admins) === "string") { + resp.admins = JSON.parse(resp.admins); + } + if (typeof (resp.owners) === "string") { + resp.owners = JSON.parse(resp.owners); + } + resolve(resp); + }) + .catch(ex => { reject(ex); }); }); } diff --git a/src/loadForm.ts b/src/loadForm.ts index f14f45e..cc5dec8 100644 --- a/src/loadForm.ts +++ b/src/loadForm.ts @@ -14,30 +14,30 @@ export class LoadForm { private _onLoadOneDrive: () => void = null; // Renders the modal - constructor(elForm: HTMLElement, elFooter: HTMLElement, hideLoadAdminOwnerBtn: boolean = false, hideLoadOneDriveBtn: boolean = false, onSuccess: () => void, onLoadOneDrive: () => void) { + constructor(elForm: HTMLElement, elFooter: HTMLElement, flowGetSiteAdminsAndOwners: string, hideLoadOneDriveBtn: boolean = false, onSuccess: () => void, onLoadOneDrive: () => void) { this._onSuccess = onSuccess; this._onLoadOneDrive = onLoadOneDrive; // Render the form and footer this.renderForm(elForm); - this.renderFooter(elFooter, hideLoadAdminOwnerBtn, hideLoadOneDriveBtn); + this.renderFooter(elFooter, flowGetSiteAdminsAndOwners, hideLoadOneDriveBtn); } // Renders the footer - private renderFooter(el: HTMLElement, hideLoadAdminOwnerBtn: boolean, hideLoadOneDriveBtn: boolean) { + private renderFooter(el: HTMLElement, flowGetSiteAdminsAndOwners: string, hideLoadOneDriveBtn: boolean) { // Clear the element while (el.firstChild) { el.removeChild(el.firstChild); } // Set the tooltips let tooltips = [] - if (!hideLoadAdminOwnerBtn) { + if (flowGetSiteAdminsAndOwners) { tooltips.push({ content: "Views the owners/admins of the site.", btnProps: { text: "View Admins/Owners", onClick: () => { // Load the admins/owners of the site - this.viewAdminsOwners(); + this.viewAdminsOwners(flowGetSiteAdminsAndOwners); } } }); @@ -265,7 +265,7 @@ export class LoadForm { } // Shows the modal - static showModal(hideLoadAdminOwnerBtn: boolean = false, hideLoadOneDriveBtn: boolean = false, onSuccess: () => void, onLoadOneDrive: () => void) { + static showModal(flowGetSiteAdminsAndOwners: string, hideLoadOneDriveBtn: boolean = false, onSuccess: () => void, onLoadOneDrive: () => void) { // Clear the modal Modal.clear(); @@ -273,7 +273,7 @@ export class LoadForm { Modal.setHeader("Load Site"); // Create the form/footer - new LoadForm(Modal.BodyElement, Modal.FooterElement, hideLoadAdminOwnerBtn, hideLoadOneDriveBtn, onSuccess, onLoadOneDrive); + new LoadForm(Modal.BodyElement, Modal.FooterElement, flowGetSiteAdminsAndOwners, hideLoadOneDriveBtn, onSuccess, onLoadOneDrive); // Show the modal Modal.show(); @@ -322,19 +322,22 @@ export class LoadForm { } // Views the admins/owners of the site - private viewAdminsOwners() { + private viewAdminsOwners(flowGetSiteAdminsAndOwners: string) { // Validate the form if (this._form.isValid()) { let ctrl = this._form.getControl("url"); let url = this._form.getValues()["url"]; // Show a loading dialog - LoadingDialog.setHeader("Validating Site"); - LoadingDialog.setBody("This will close after the site url is validated..."); + LoadingDialog.setHeader("Calling Flow"); + LoadingDialog.setBody("This will close after the flow has completed..."); LoadingDialog.show(); + // Set the absolute url of the site + let siteUrl = document.location.origin + (typeof (url) === "string" ? url : url.value); + // Load the admins/owners - DataSource.loadAdminsOwners(typeof (url) === "string" ? url : url.value).then( + DataSource.loadAdminsOwners(flowGetSiteAdminsAndOwners, siteUrl).then( // Success users => { // Clear the modal @@ -347,6 +350,13 @@ export class LoadForm { new DataTable({ el: Modal.BodyElement, rows: users.admins.concat(users.owners), + onRendering: dtProps => { + // Order by the 1st column + dtProps.order = [[0, "asc"]]; + + // Return the properties + return dtProps; + }, columns: [ { name: "type",