Skip to content

CPV Tweaks #1885

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Nov 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions src/components/forms/RFFComponents.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@ import {
CTooltip,
} from '@coreui/react'
import Select from 'react-select'
import AsyncSelect from 'react-select/async'
import { Field } from 'react-final-form'
import { FieldArray } from 'react-final-form-arrays'
import React from 'react'
import React, { useState, useMemo, useRef } from 'react'
import PropTypes from 'prop-types'
import { useRef } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { debounce } from 'lodash'

/*
wrapper classes for React Final Form with CoreUI
Expand Down Expand Up @@ -377,14 +376,33 @@ export const RFFSelectSearch = ({
placeholder,
validate,
onChange,
onInputChange,
multi,
disabled = false,
retainInput = false,
isLoading = false,
}) => {
const [inputText, setInputText] = useState('')
const selectSearchvalues = values.map((val) => ({
value: val.value,
label: val.name,
}))

const debounceOnInputChange = useMemo(() => {
if (onInputChange) {
return debounce(onInputChange, 1000)
}
}, [])

const setOnInputChange = (e, action) => {
if (retainInput && action.action !== 'set-value') {
setInputText(e)
}
if (onInputChange && action.action === 'input-change') {
debounceOnInputChange(e)
}
}

return (
<Field name={name} validate={validate}>
{({ meta, input }) => {
Expand All @@ -404,6 +422,9 @@ export const RFFSelectSearch = ({
placeholder={placeholder}
isMulti={multi}
onChange={onChange}
onInputChange={debounceOnInputChange}
inputValue={inputText}
isLoading={isLoading}
/>
)}
{!onChange && (
Expand All @@ -417,7 +438,10 @@ export const RFFSelectSearch = ({
disabled={disabled}
options={selectSearchvalues}
placeholder={placeholder}
onInputChange={setOnInputChange}
isMulti={multi}
inputValue={inputText}
isLoading={isLoading}
/>
)}
{meta.error && meta.touched && <span className="text-danger">{meta.error}</span>}
Expand All @@ -432,6 +456,8 @@ RFFSelectSearch.propTypes = {
...sharedPropTypes,
multi: PropTypes.bool,
placeholder: PropTypes.string,
onInputChange: PropTypes.func,
isLoading: PropTypes.bool,
values: PropTypes.arrayOf(PropTypes.shape({ value: PropTypes.string, name: PropTypes.string }))
.isRequired,
}
45 changes: 39 additions & 6 deletions src/components/tables/CippTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import {
CModalTitle,
CCallout,
CFormSelect,
CAccordion,
CAccordionHeader,
CAccordionBody,
CAccordionItem,
} from '@coreui/react'
import DataTable, { createTheme } from 'react-data-table-component'
import PropTypes from 'prop-types'
Expand Down Expand Up @@ -665,12 +669,41 @@ export default function CippTable({
<>
{(massResults.length >= 1 || loopRunning) && (
<CCallout color="info">
{massResults.map((message, idx) => {
const results = message.data?.Results
const displayResults = Array.isArray(results) ? results.join(', ') : results

return <li key={`message-${idx}`}>{displayResults}</li>
})}
{massResults[0]?.data?.Metadata?.Heading && (
<CAccordion flush>
{massResults.map((message, idx) => {
const results = message.data?.Results
const displayResults = Array.isArray(results)
? results.join('</li><li>')
: results
var iconName = 'info-circle'
if (message.data?.Metadata?.Success === true) {
iconName = 'check-circle'
} else if (message.data?.Metadata?.Success === false) {
iconName = 'times-circle'
}
return (
<CAccordionItem>
<CAccordionHeader>
<FontAwesomeIcon icon={iconName} className="me-2" />
{message.data?.Metadata?.Heading}
</CAccordionHeader>
<CAccordionBody>
{results.map((line, i) => {
return <li key={i}>{line}</li>
})}
</CAccordionBody>
</CAccordionItem>
)
})}
</CAccordion>
)}
{!massResults[0]?.data?.Metadata?.Heading &&
massResults.map((message, idx) => {
const results = message.data?.Results
const displayResults = Array.isArray(results) ? results.join(', ') : results
return <li key={`message-${idx}`}>{displayResults}</li>
})}
{loopRunning && (
<li>
<CSpinner size="sm" />
Expand Down
30 changes: 30 additions & 0 deletions src/components/utilities/CippGraphUserFilter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
function CippGraphUserFilter(query) {
const properties = [
'displayName',
'givenName',
'surname',
'userPrincipalName',
'mail',
'mailNickname',
]
const endsWithProperties = ['mail', 'otherMails', 'userPrincipalName', 'proxyAddresses']
const multiValueProperties = ['proxyAddresses']

var filterConditions = []
properties.map((property) => {
filterConditions.push(`startsWith(${property},'${query}')`)
if (endsWithProperties.includes(property)) {
filterConditions.push(`endsWith(${property},'${query}')`)
}
})
multiValueProperties.map((property) => {
filterConditions.push(`${property}/any(a:startsWith(a,'${query}'))`)
if (endsWithProperties.includes(property)) {
filterConditions.push(`${property}/any(a:endsWith(a,'${query}'))`)
}
})

return filterConditions.join(' or ')
}

export default CippGraphUserFilter
20 changes: 17 additions & 3 deletions src/views/cipp/CIPPSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -595,12 +595,14 @@ const ExcludedTenantsSettings = () => {
onConfirm: () => removeExcludeTenant(domain),
})

const handleCPVPermissions = (domain) =>
const handleCPVPermissions = (domain, resetsp = false) =>
ModalService.confirm({
title: 'Refresh Permissions',
body: <div>Are you sure you want to refresh permissions for {domain.defaultDomainName}?</div>,
onConfirm: () =>
refreshPermissions({ path: `/api/ExecCPVPermissions?TenantFilter=${domain.customerId}` }),
refreshPermissions({
path: `/api/ExecCPVPermissions?TenantFilter=${domain.customerId}&ResetSP=${resetsp}`,
}),
})
const handleConfirmExcludeTenant = (tenant) => {
ModalService.confirm({
Expand Down Expand Up @@ -681,7 +683,12 @@ const ExcludedTenantsSettings = () => {
</CTooltip>
)}
<CTooltip content="CPV Refresh">
<CButton size="sm" variant="ghost" color="info" onClick={() => handleCPVPermissions(row)}>
<CButton
size="sm"
variant="ghost"
color="info"
onClick={() => handleCPVPermissions(row, false)}
>
<FontAwesomeIcon icon={faRecycle} href="" />
</CButton>
</CTooltip>
Expand Down Expand Up @@ -799,6 +806,13 @@ const ExcludedTenantsSettings = () => {
modalMessage:
'Are you sure you want to refresh the CPV permissions for these tenants?',
},
{
label: 'Reset CPV Permissions',
modal: true,
modalUrl: `/api/ExecCPVPermissions?TenantFilter=!customerId&ResetSP=true`,
modalMessage:
'Are you sure you want to reset the CPV permissions for these tenants? (This will delete the Service Principal and re-add it.)',
},
],
},
isModal: true,
Expand Down
14 changes: 9 additions & 5 deletions src/views/email-exchange/tools/MailboxRestoreWizard.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useState } from 'react'
import { CCallout, CCol, CListGroup, CListGroupItem, CRow, CSpinner } from '@coreui/react'
import { Field, FormSpy } from 'react-final-form'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
Expand Down Expand Up @@ -38,21 +38,22 @@ Error.propTypes = {

const MailboxRestoreWizard = () => {
const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName)
const [anrFilter, setAnrFilter] = useState('')
const {
data: sourceMailboxes = [],
isFetching: sMailboxesIsFetching,
error: sMailboxError,
} = useGenericGetRequestQuery({
path: '/api/ListMailboxes',
params: { TenantFilter: tenantDomain, SoftDeletedMailbox: true },
params: { TenantFilter: tenantDomain, SoftDeletedMailbox: true, SkipLicense: true },
})
const {
data: targetMailboxes = [],
isFetching: tMailboxesIsFetching,
error: tMailboxError,
} = useGenericGetRequestQuery({
path: '/api/ListMailboxes',
params: { TenantFilter: tenantDomain },
params: { TenantFilter: tenantDomain, Anr: anrFilter, SkipLicense: true },
})
const currentSettings = useSelector((state) => state.app)
const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
Expand Down Expand Up @@ -104,6 +105,7 @@ const MailboxRestoreWizard = () => {
}))}
placeholder={!sMailboxesIsFetching ? 'Select mailbox' : 'Loading...'}
name="SourceMailbox"
isLoading={sMailboxesIsFetching}
/>
{sMailboxError && <span>Failed to load source mailboxes</span>}
</div>
Expand All @@ -118,12 +120,14 @@ const MailboxRestoreWizard = () => {
<div className="mb-2">
<RFFSelectSearch
label={'Mailboxes in ' + tenantDomain}
name="TargetMailbox"
values={targetMailboxes?.map((mbx) => ({
value: mbx.ExchangeGuid,
name: `${mbx.displayName} <${mbx.UPN}>`,
}))}
placeholder={!tMailboxesIsFetching ? 'Select mailbox' : 'Loading...'}
name="TargetMailbox"
retainInput={true}
onInputChange={setAnrFilter}
isLoading={tMailboxesIsFetching}
/>
{sMailboxError && <span>Failed to load source mailboxes</span>}
</div>
Expand Down