diff --git a/Tools/Start-CippDevEmulators.ps1 b/Tools/Start-CippDevEmulators.ps1
index b4f6ca696ffe..bde7511caac1 100644
--- a/Tools/Start-CippDevEmulators.ps1
+++ b/Tools/Start-CippDevEmulators.ps1
@@ -1,4 +1,12 @@
-Write-Host "Starting CIPP Dev Emulators"
+Write-Host 'Starting CIPP Dev Emulators'
+Get-Process node -ErrorAction SilentlyContinue | Stop-Process -ErrorAction SilentlyContinue
$Path = (Get-Item $PSScriptRoot).Parent.Parent.FullName
-wt --title CIPP`; new-tab --title 'Azurite' -d $Path pwsh -c azurite`; new-tab --title 'FunctionApp' -d $Path\CIPP-API pwsh -c func start`; new-tab --title 'CIPP Frontend' -d $Path\CIPP pwsh -c npm run start`; new-tab --title 'SWA' -d $Path\CIPP pwsh -c npm run start-swa
+
+$Process = Read-Host -Prompt 'Start Process Function (y/N)?'
+
+if ($Process -eq 'y') {
+ wt --title CIPP`; new-tab --title 'Azurite' -d $Path pwsh -c azurite`; new-tab --title 'FunctionApp' -d $Path\CIPP-API pwsh -c func start`; new-tab --title 'CIPP Frontend' -d $Path\CIPP pwsh -c npm run start`; new-tab --title 'SWA' -d $Path\CIPP pwsh -c npm run start-swa`; new-tab --title 'CIPP-API-Processor' -d $Path\CIPP-API-Processor pwsh -c func start --port 7072
+} else {
+ wt --title CIPP`; new-tab --title 'Azurite' -d $Path pwsh -c azurite`; new-tab --title 'FunctionApp' -d $Path\CIPP-API pwsh -c func start`; new-tab --title 'CIPP Frontend' -d $Path\CIPP pwsh -c npm run start`; new-tab --title 'SWA' -d $Path\CIPP pwsh -c npm run start-swa
+}
diff --git a/package.json b/package.json
index 41b4ceedb492..b74d149fe82e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "cipp",
- "version": "6.2.2",
+ "version": "6.3.0",
"description": "The CyberDrain Improved Partner Portal is a portal to help manage administration for Microsoft Partners.",
"homepage": "https://cipp.app/",
"bugs": {
diff --git a/public/version_latest.txt b/public/version_latest.txt
index ca06394388d6..798e38995c4d 100644
--- a/public/version_latest.txt
+++ b/public/version_latest.txt
@@ -1 +1 @@
-6.2.2
+6.3.0
diff --git a/src/components/contentcards/CippButtonCard.jsx b/src/components/contentcards/CippButtonCard.jsx
index 8e74d5470693..a34acbd66142 100644
--- a/src/components/contentcards/CippButtonCard.jsx
+++ b/src/components/contentcards/CippButtonCard.jsx
@@ -8,7 +8,7 @@ export default function CippButtonCard({
titleType = 'normal',
CardButton,
children,
- isFetching,
+ isFetching = false,
className = 'h-100',
}) {
return (
@@ -22,7 +22,7 @@ export default function CippButtonCard({
{isFetching && }
{children}
- {CardButton}
+ {CardButton && {CardButton}}
)
}
@@ -30,8 +30,8 @@ export default function CippButtonCard({
CippButtonCard.propTypes = {
title: PropTypes.string.isRequired,
titleType: PropTypes.string,
- CardButton: PropTypes.element.isRequired,
+ CardButton: PropTypes.element,
children: PropTypes.element.isRequired,
- isFetching: PropTypes.bool.isRequired,
+ isFetching: PropTypes.bool,
className: PropTypes.string,
}
diff --git a/src/components/forms/RFFComponents.jsx b/src/components/forms/RFFComponents.jsx
index 14163a033d62..30e096a71aa8 100644
--- a/src/components/forms/RFFComponents.jsx
+++ b/src/components/forms/RFFComponents.jsx
@@ -516,6 +516,7 @@ export const RFFSelectSearch = ({
retainInput = true,
isLoading = false,
allowCreate = false,
+ onCreateOption,
refreshFunction,
...props
}) => {
@@ -589,7 +590,7 @@ export const RFFSelectSearch = ({
)}
{allowCreate ? (
-
+
) : (
)}
@@ -612,6 +613,9 @@ RFFSelectSearch.propTypes = {
onInputChange: PropTypes.func,
isLoading: PropTypes.bool,
refreshFunction: PropTypes.func,
+ allowCreate: PropTypes.bool,
+ onCreateOption: PropTypes.func,
+ retainInput: PropTypes.bool,
values: PropTypes.arrayOf(PropTypes.shape({ value: PropTypes.string, name: PropTypes.string }))
.isRequired,
}
diff --git a/src/components/tables/CellGenericFormat.jsx b/src/components/tables/CellGenericFormat.jsx
index 465a7dd25f37..27f06c04906a 100644
--- a/src/components/tables/CellGenericFormat.jsx
+++ b/src/components/tables/CellGenericFormat.jsx
@@ -24,13 +24,17 @@ function nocolour(iscolourless, content) {
export function CellTip(cell, overflow = false) {
return (
- {String(cell)}
+ {overflow ? (
+ {String(cell)}
+ ) : (
+ {String(cell)}
+ )}
)
}
export const cellGenericFormatter =
- ({ warning = false, reverse = false, colourless = true, noDataIsFalse } = {}) =>
+ ({ warning = false, reverse = false, colourless = true, noDataIsFalse, wrap = false } = {}) =>
// eslint-disable-next-line react/display-name
(row, index, column, id) => {
const cell = column.selector(row)
@@ -51,6 +55,10 @@ export const cellGenericFormatter =
)
}
+
+ if (wrap) {
+ return CellTip(cell, true)
+ }
return CellTip(cell)
}
if (typeof cell === 'number') {
diff --git a/src/components/tables/CellTable.jsx b/src/components/tables/CellTable.jsx
index cfcbd5403065..2186a406fe6b 100644
--- a/src/components/tables/CellTable.jsx
+++ b/src/components/tables/CellTable.jsx
@@ -23,6 +23,15 @@ export default function cellTable(
if (columnProp === undefined || columnProp === null) {
columnProp = []
} else {
+ var objectLength = 1
+ var lengthText = 'Item'
+ if (columnProp instanceof Array) {
+ objectLength = columnProp.length
+ if (objectLength > 1) {
+ lengthText = 'Items'
+ }
+ }
+
if (!Array.isArray(columnProp) && typeof columnProp === 'object') {
columnProp = Object.keys(columnProp).map((key) => {
return {
@@ -93,7 +102,7 @@ export default function cellTable(
size="sm"
onClick={() => handleTable({ columnProp })}
>
- {columnProp.length} Items
+ {objectLength} {lengthText}
)
}
diff --git a/src/components/tables/CellTip.jsx b/src/components/tables/CellTip.jsx
index 399d504fc2eb..e6f5c403b968 100644
--- a/src/components/tables/CellTip.jsx
+++ b/src/components/tables/CellTip.jsx
@@ -23,11 +23,11 @@ export const CellTipButton = (value, display) => {
)
}
-export const CellTip = (value, overflow = false) => {
+export const CellTip = (value, wrap = false) => {
if (!value) {
return
}
- if (!overflow) {
+ if (!wrap) {
return (
{String(value)}
diff --git a/src/components/tables/CippTable.jsx b/src/components/tables/CippTable.jsx
index 04ede5a4c204..698ce52d176a 100644
--- a/src/components/tables/CippTable.jsx
+++ b/src/components/tables/CippTable.jsx
@@ -63,7 +63,7 @@ const FilterComponent = ({ filterText, onFilter, onClear, filterlist, onFilterPr
{filterlist &&
filterlist.map((item, idx) => {
return (
- onFilterPreset(item.filter)}>
+ onFilterPreset(item.filter)}>
{item.filterName}
)
@@ -722,7 +722,7 @@ export default function CippTable({
{dataKeys() &&
dataKeys().map((item, idx) => {
return (
- addColumn(item)}>
+ addColumn(item)}>
{updatedColumns.find(
(o) => o.exportSelector === item && o?.omit !== true,
) && }{' '}
@@ -820,7 +820,7 @@ export default function CippTable({
{actionsList.map((item, idx) => {
return (
- executeselectedAction(item)}>
+ executeselectedAction(item)}>
{item.label}
)
@@ -885,6 +885,7 @@ export default function CippTable({
updatedColumns,
addColumn,
setGraphFilter,
+ isFetching,
])
const tablePageSize = useSelector((state) => state.app.tablePageSize)
const [codeCopied, setCodeCopied] = useState(false)
@@ -950,8 +951,8 @@ export default function CippTable({
const results = message.data?.Results
const displayResults = Array.isArray(results) ? results.join(', ') : results
return (
- <>
-
+
+
{displayResults}
onCodeCopied()}>
- >
+
)
})}
{loopRunning && (
@@ -1008,11 +1009,12 @@ export default function CippTable({
progressPending={isFetching}
progressComponent={}
paginationRowsPerPageOptions={[25, 50, 100, 200, 500]}
+ keyField={keyField}
{...rest}
/>
{selectedRows.length >= 1 && Selected {selectedRows.length} items}
setCodeOffcanvasVisible(false)}
diff --git a/src/components/utilities/CippActionsOffcanvas.jsx b/src/components/utilities/CippActionsOffcanvas.jsx
index e543a511ffb1..7287af9722da 100644
--- a/src/components/utilities/CippActionsOffcanvas.jsx
+++ b/src/components/utilities/CippActionsOffcanvas.jsx
@@ -38,11 +38,11 @@ import { faGlobe } from '@fortawesome/free-solid-svg-icons'
import { cellGenericFormatter } from '../tables/CellGenericFormat'
import ReactSelect from 'react-select'
-const CippOffcanvasCard = ({ action, key }) => {
+const CippOffcanvasCard = ({ action }) => {
const [offcanvasVisible, setOffcanvasVisible] = useState(false)
return (
<>
-
+
Report Name: {action.label}
@@ -95,7 +95,6 @@ const CippOffcanvasCard = ({ action, key }) => {
}
CippOffcanvasCard.propTypes = {
action: PropTypes.object,
- key: PropTypes.object,
}
export default function CippActionsOffcanvas(props) {
diff --git a/src/components/utilities/CippAppPermissionBuilder.jsx b/src/components/utilities/CippAppPermissionBuilder.jsx
new file mode 100644
index 000000000000..c986b67145f2
--- /dev/null
+++ b/src/components/utilities/CippAppPermissionBuilder.jsx
@@ -0,0 +1,962 @@
+import React, { useEffect, useRef, useState, useCallback } from 'react'
+import {
+ CButton,
+ CCallout,
+ CCol,
+ CForm,
+ CRow,
+ CAccordion,
+ CAccordionHeader,
+ CAccordionBody,
+ CAccordionItem,
+ CTooltip,
+} from '@coreui/react'
+import { Field, Form, FormSpy } from 'react-final-form'
+import { RFFCFormRadioList, RFFSelectSearch } from 'src/components/forms'
+import { useGenericGetRequestQuery, useLazyGenericGetRequestQuery } from 'src/store/api/app'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import {
+ TenantSelectorMultiple,
+ ModalService,
+ CippOffcanvas,
+ CippCodeBlock,
+} from 'src/components/utilities'
+import PropTypes from 'prop-types'
+import { OnChange } from 'react-final-form-listeners'
+import { useListTenantsQuery } from 'src/store/api/tenants'
+import CippButtonCard from 'src/components/contentcards/CippButtonCard'
+import { CippTable } from '../tables'
+import { Row } from 'react-bootstrap'
+import { cellGenericFormatter } from '../tables/CellGenericFormat'
+import Skeleton from 'react-loading-skeleton'
+import CippDropzone from './CippDropzone'
+import { Editor } from '@monaco-editor/react'
+import { useSelector } from 'react-redux'
+import { CippCallout } from '../layout'
+
+const CippAppPermissionBuilder = ({ onSubmit, currentPermissions = {}, isSubmitting }) => {
+ const [selectedApp, setSelectedApp] = useState([])
+ const [permissionsImported, setPermissionsImported] = useState(false)
+ const [newPermissions, setNewPermissions] = useState({})
+ const [importedManifest, setImportedManifest] = useState(null)
+ const [manifestVisible, setManifestVisible] = useState(false)
+ const currentTheme = useSelector((state) => state.app.currentTheme)
+ const [calloutMessage, setCalloutMessage] = useState(null)
+
+ const {
+ data: servicePrincipals = [],
+ isFetching: spFetching,
+ isSuccess: spSuccess,
+ isUninitialized: spUninitialized,
+ refetch: refetchSpList,
+ } = useGenericGetRequestQuery({
+ path: 'api/ExecServicePrincipals',
+ })
+
+ const [createServicePrincipal, createResult] = useLazyGenericGetRequestQuery()
+
+ const removeServicePrincipal = (appId) => {
+ var servicePrincipal = selectedApp.find((sp) => sp?.appId === appId)
+ var newServicePrincipals = selectedApp.filter((sp) => sp?.appId !== appId)
+
+ ModalService.confirm({
+ title: 'Remove Service Principal',
+ body: `Are you sure you want to remove ${servicePrincipal.displayName}?`,
+ onConfirm: () => {
+ setSelectedApp(newServicePrincipals)
+ var updatedPermissions = JSON.parse(JSON.stringify(newPermissions))
+ delete updatedPermissions.Permissions[appId]
+ setNewPermissions(updatedPermissions)
+ },
+ })
+ }
+
+ const confirmReset = () => {
+ ModalService.confirm({
+ title: 'Reset to Default',
+ body: 'Are you sure you want to reset all permissions to default?',
+ onConfirm: () => {
+ setSelectedApp([])
+ setPermissionsImported(false)
+ setManifestVisible(false)
+ setCalloutMessage('Permissions reset to default.')
+ },
+ })
+ }
+
+ const handleSubmit = (values) => {
+ if (onSubmit) {
+ var postBody = {
+ Permissions: newPermissions,
+ }
+ onSubmit(postBody)
+ }
+ }
+
+ const onCreateServicePrincipal = (appId) => {
+ createServicePrincipal({
+ path: 'api/ExecServicePrincipals?Action=Create&AppId=' + appId,
+ }).then(() => {
+ refetchSpList()
+ setCalloutMessage(createResult?.data?.Results)
+ })
+ }
+
+ const addPermissionRow = (servicePrincipal, permissionType, permission) => {
+ var updatedPermissions = JSON.parse(JSON.stringify(newPermissions))
+
+ if (!updatedPermissions?.Permissions[servicePrincipal]) {
+ updatedPermissions.Permissions[servicePrincipal] = {
+ applicationPermissions: [],
+ delegatedPermissions: [],
+ }
+ }
+ var currentPermission = updatedPermissions?.Permissions[servicePrincipal][permissionType]
+ var newPermission = []
+ if (currentPermission) {
+ currentPermission.map((perm) => {
+ if (perm.id !== permission.value) {
+ newPermission.push(perm)
+ }
+ })
+ }
+ newPermission.push({ id: permission.value, value: permission.label })
+
+ updatedPermissions.Permissions[servicePrincipal][permissionType] = newPermission
+ setNewPermissions(updatedPermissions)
+ }
+
+ const removePermissionRow = (servicePrincipal, permissionType, permissionId, permissionValue) => {
+ // modal confirm
+ ModalService.confirm({
+ title: 'Remove Permission',
+ body: `Are you sure you want to remove the permission: ${permissionValue}?`,
+ onConfirm: () => {
+ var updatedPermissions = JSON.parse(JSON.stringify(newPermissions))
+ var currentPermission = updatedPermissions?.Permissions[servicePrincipal][permissionType]
+ var newPermission = []
+ if (currentPermission) {
+ currentPermission.map((perm) => {
+ if (perm.id !== permissionId) {
+ newPermission.push(perm)
+ }
+ })
+ }
+ updatedPermissions.Permissions[servicePrincipal][permissionType] = newPermission
+ setNewPermissions(updatedPermissions)
+ },
+ })
+ }
+
+ const generateManifest = (appDisplayName = 'CIPP-SAM', prompt = false) => {
+ if (prompt) {
+ // modal input form for appDisplayName
+ ModalService.prompt({
+ title: 'Generate Manifest',
+ body: 'Please enter the display name for the application.',
+ onConfirm: (value) => {
+ generateManifest({ appDisplayName: value })
+ },
+ })
+ } else {
+ var manifest = {
+ isFallbackPublicClient: true,
+ signInAudience: 'AzureADMultipleOrgs',
+ displayName: appDisplayName,
+ web: {
+ redirectUris: [
+ 'https://login.microsoftonline.com/common/oauth2/nativeclient',
+ 'https://localhost',
+ 'http://localhost',
+ 'http://localhost:8400',
+ ],
+ },
+ requiredResourceAccess: [],
+ }
+
+ var additionalPermissions = []
+
+ selectedApp.map((sp) => {
+ var appRoles = newPermissions?.Permissions[sp.appId]?.applicationPermissions
+ var delegatedPermissions = newPermissions?.Permissions[sp.appId]?.delegatedPermissions
+ var requiredResourceAccess = {
+ resourceAppId: sp.appId,
+ resourceAccess: [],
+ }
+ var additionalRequiredResourceAccess = {
+ resourceAppId: sp.appId,
+ resourceAccess: [],
+ }
+ if (appRoles) {
+ appRoles.map((role) => {
+ requiredResourceAccess.resourceAccess.push({
+ id: role.id,
+ type: 'Role',
+ })
+ })
+ }
+ if (delegatedPermissions) {
+ delegatedPermissions.map((perm) => {
+ // permission not a guid skip
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(perm.id)) {
+ requiredResourceAccess.resourceAccess.push({
+ id: perm.id,
+ type: 'Scope',
+ })
+ } else {
+ additionalRequiredResourceAccess.resourceAccess.push({
+ id: perm.id,
+ type: 'Scope',
+ })
+ }
+ })
+ }
+ if (requiredResourceAccess.resourceAccess.length > 0) {
+ manifest.requiredResourceAccess.push(requiredResourceAccess)
+ }
+ if (additionalRequiredResourceAccess.resourceAccess.length > 0) {
+ additionalPermissions.push(additionalRequiredResourceAccess)
+ }
+ })
+
+ var fileName = `${appDisplayName.replace(' ', '-')}.json`
+ if (appDisplayName === 'CIPP-SAM') {
+ fileName = 'SAMManifest.json'
+ }
+
+ var blob = new Blob([JSON.stringify(manifest, null, 2)], { type: 'application/json' })
+ var url = URL.createObjectURL(blob)
+ var a = document.createElement('a')
+ a.href = url
+ a.download = `${fileName}`
+ a.click()
+ URL.revokeObjectURL(url)
+
+ if (additionalPermissions.length > 0) {
+ ModalService.confirm({
+ title: 'Additional Permissions',
+ body: 'Some permissions are not supported in the manifest. Would you like to download them?',
+ confirmLabel: 'Download',
+ onConfirm: () => {
+ var additionalBlob = new Blob([JSON.stringify(additionalPermissions, null, 2)], {
+ type: 'application/json',
+ })
+ var additionalUrl = URL.createObjectURL(additionalBlob)
+ var additionalA = document.createElement('a')
+ additionalA.href = additionalUrl
+ additionalA.download = 'AdditionalPermissions.json'
+ additionalA.click()
+ URL.revokeObjectURL(additionalUrl)
+ },
+ })
+ }
+ }
+ }
+
+ const importManifest = () => {
+ var updatedPermissions = { Permissions: {} }
+ var manifest = importedManifest
+ var requiredResourceAccess = manifest.requiredResourceAccess
+ var selectedServicePrincipals = []
+
+ requiredResourceAccess.map((resourceAccess) => {
+ var sp = servicePrincipals?.Results?.find((sp) => sp.appId === resourceAccess.resourceAppId)
+ if (sp) {
+ var appRoles = []
+ var delegatedPermissions = []
+ selectedServicePrincipals.push(sp)
+ resourceAccess.resourceAccess.map((access) => {
+ if (access.type === 'Role') {
+ var role = sp.appRoles.find((role) => role.id === access.id)
+ if (role) {
+ appRoles.push({
+ id: role.id,
+ value: role.value,
+ })
+ }
+ } else if (access.type === 'Scope') {
+ var scope = sp.publishedPermissionScopes.find((scope) => scope.id === access.id)
+ if (scope) {
+ delegatedPermissions.push({
+ id: scope.id,
+ value: scope.value,
+ })
+ }
+ }
+ })
+ updatedPermissions.Permissions[sp.appId] = {
+ applicationPermissions: appRoles,
+ delegatedPermissions: delegatedPermissions,
+ }
+ }
+ })
+ setNewPermissions(updatedPermissions)
+ setSelectedApp(selectedServicePrincipals)
+ setImportedManifest(null)
+ setPermissionsImported(true)
+ setManifestVisible(false)
+ setCalloutMessage('Manifest imported successfully.')
+ }
+
+ const onManifestImport = useCallback((acceptedFiles) => {
+ acceptedFiles.forEach((file) => {
+ const reader = new FileReader()
+ reader.onabort = () => console.log('file reading was aborted')
+ reader.onerror = () => console.log('file reading has failed')
+ reader.onload = () => {
+ console.log(reader.result)
+ try {
+ var manifest = JSON.parse(reader.result)
+ setImportedManifest(manifest)
+ console.log(importedManifest)
+ } catch {
+ console.log('invalid manifest')
+ }
+ }
+ reader.readAsText(file)
+ })
+ }, [])
+
+ useEffect(() => {
+ try {
+ var initialAppIds = Object.keys(currentPermissions?.Permissions)
+ } catch {
+ initialAppIds = []
+ }
+
+ if (spSuccess && selectedApp.length == 0 && initialAppIds.length == 0) {
+ var microsoftGraph = servicePrincipals?.Results?.find(
+ (sp) => sp?.appId === '00000003-0000-0000-c000-000000000000',
+ )
+ setSelectedApp([microsoftGraph])
+ setNewPermissions({
+ Permissions: {
+ '00000003-0000-0000-c000-000000000000': {
+ applicationPermissions: [],
+ delegatedPermissions: [],
+ },
+ },
+ })
+ } else if (spSuccess && initialAppIds.length > 0 && permissionsImported == false) {
+ var newApps = []
+ initialAppIds?.map((appId) => {
+ var newSp = servicePrincipals?.Results?.find((sp) => sp.appId === appId)
+ if (newSp) {
+ newApps.push(newSp)
+ }
+ })
+ newApps = newApps.sort((a, b) => {
+ return a.displayName.localeCompare(b.displayName)
+ })
+ setSelectedApp(newApps)
+ setNewPermissions(currentPermissions)
+ setPermissionsImported(true)
+ }
+ }, [
+ currentPermissions,
+ permissionsImported,
+ spSuccess,
+ selectedApp,
+ servicePrincipals,
+ setSelectedApp,
+ setPermissionsImported,
+ setNewPermissions,
+ ])
+
+ const ApiPermissionRow = ({ servicePrincipal = null }) => {
+ return (
+ <>
+ {spSuccess && servicePrincipal !== null && (
+
+
+
+
+
+ Manage the permissions for the {servicePrincipal.displayName}.
+
+
+
+
+
+ {
+ removeServicePrincipal(servicePrincipal.appId)
+ }}
+ disabled={servicePrincipal.appId === '00000003-0000-0000-c000-000000000000'}
+ className={`circular-button`}
+ >
+
+
+
+
+
+
+
+
+ {servicePrincipal?.appRoles?.length > 0 ? (
+ <>
+
+
+ {({ values }) => {
+ return (
+
+
+ {
+ return newPermissions?.Permissions[
+ servicePrincipal.appId
+ ]?.applicationPermissions?.find(
+ (perm) => perm.id === role.id,
+ )
+ ? false
+ : true
+ })
+ .map((role) => ({
+ name: role.value,
+ value: role.id,
+ }))
+ .sort((a, b) => a.name.localeCompare(b.name))}
+ />
+
+
+
+ {
+ addPermissionRow(
+ servicePrincipal.appId,
+ 'applicationPermissions',
+ values.Permissions[servicePrincipal?.appId]
+ .applicationPermissions,
+ )
+ values.Permissions[
+ servicePrincipal.appId
+ ].applicationPermissions = ''
+ }}
+ className={`circular-button`}
+ >
+
+
+
+
+
+ )
+ }}
+
+
+
+ row.value,
+ name: 'Permission',
+ sortable: true,
+ exportSelector: 'value',
+ maxWidth: '30%',
+ cell: cellGenericFormatter(),
+ },
+ {
+ selector: (row) => row.id,
+ name: 'Id',
+ omit: true,
+ exportSelector: 'id',
+ },
+ {
+ selector: (row) =>
+ servicePrincipal.appRoles.find((role) => role.id === row.id)
+ .description,
+ name: 'Description',
+ cell: cellGenericFormatter({ wrap: true }),
+ maxWidth: '60%',
+ },
+ {
+ name: 'Actions',
+ cell: (row) => {
+ return (
+
+ {
+ removePermissionRow(
+ servicePrincipal.appId,
+ 'applicationPermissions',
+ row.id,
+ row.value,
+ )
+ }}
+ color="danger"
+ variant="ghost"
+ size="sm"
+ >
+
+
+
+ )
+ },
+ maxWidth: '10%',
+ },
+ ]}
+ dynamicColumns={false}
+ />
+
+ >
+ ) : (
+ <>
+
+
+ No Application Permissions found.
+
+ >
+ )}
+
+
+
+
+
+
+
+
+
+ {servicePrincipal?.publishedPermissionScopes?.length == 0 && (
+
+
+ No Published Delegated Permissions found.
+
+ )}
+
+ {({ values }) => {
+ return (
+
+
+ 0
+ ? servicePrincipal?.publishedPermissionScopes
+ .filter((scopes) => {
+ return newPermissions?.Permissions[
+ servicePrincipal.appId
+ ]?.delegatedPermissions?.find(
+ (perm) => perm.id === scopes.id,
+ )
+ ? false
+ : true
+ })
+ .map((scope) => ({
+ name: scope.value,
+ value: scope.id,
+ }))
+ .sort((a, b) => a.name.localeCompare(b.name))
+ : []
+ }
+ allowCreate={true}
+ />
+
+
+
+ {
+ addPermissionRow(
+ servicePrincipal.appId,
+ 'delegatedPermissions',
+ values?.Permissions[servicePrincipal.appId]
+ .delegatedPermissions,
+ )
+ values.Permissions[servicePrincipal.appId].delegatedPermissions =
+ ''
+ }}
+ className={`circular-button`}
+ >
+
+
+
+
+
+ )
+ }}
+
+
+
+ row?.value,
+ name: 'Permission',
+ sortable: true,
+ exportSelector: 'value',
+ maxWidth: '30%',
+ cell: cellGenericFormatter(),
+ },
+ {
+ selector: (row) => row?.id,
+ name: 'Id',
+ omit: true,
+ exportSelector: 'id',
+ },
+ {
+ selector: (row) =>
+ servicePrincipal.publishedPermissionScopes.find(
+ (scope) => scope?.id === row?.id,
+ )?.userConsentDescription ?? 'No Description',
+ name: 'Description',
+ cell: cellGenericFormatter({ wrap: true }),
+ maxWidth: '60%',
+ },
+ {
+ name: 'Actions',
+ cell: (row) => {
+ return (
+
+ {
+ removePermissionRow(
+ servicePrincipal.appId,
+ 'delegatedPermissions',
+ row.id,
+ row.value,
+ )
+ }}
+ color="danger"
+ variant="ghost"
+ size="sm"
+ >
+
+
+
+ )
+ },
+ maxWidth: '10%',
+ },
+ ]}
+ dynamicColumns={false}
+ />
+
+
+
+
+
+ )}
+ >
+ )
+ }
+ ApiPermissionRow.propTypes = {
+ servicePrincipal: PropTypes.object,
+ }
+
+ return (
+ <>
+ {spFetching && }
+ {spSuccess && (
+