diff --git a/public/version_latest.txt b/public/version_latest.txt index d87edbfc1069..ecedc98d1d5a 100644 --- a/public/version_latest.txt +++ b/public/version_latest.txt @@ -1 +1 @@ -4.2.1 \ No newline at end of file +4.3.1 \ No newline at end of file diff --git a/src/_nav.js b/src/_nav.js index 2f8d05e25f85..c243b18c4e12 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -663,6 +663,11 @@ const _nav = [ name: 'Settings', to: '/cipp/settings', }, + { + component: CNavItem, + name: 'Scheduler', + to: '/cipp/scheduler', + }, { component: CNavItem, name: 'SAM Setup Wizard', diff --git a/src/adminRoutes.js b/src/adminRoutes.js index b57360064384..8b10173655ab 100644 --- a/src/adminRoutes.js +++ b/src/adminRoutes.js @@ -11,12 +11,15 @@ const GDAPRelationships = React.lazy(() => import('./views/tenant/administration/ListGDAPRelationships'), ) const appapproval = React.lazy(() => import('src/views/cipp/AppApproval')) +const Scheduler = React.lazy(() => import('src/views/cipp/Scheduler')) const adminRoutes = [ { path: '/cipp', name: 'CIPP' }, { path: '/cipp/cipp', name: 'CIPP' }, { path: '/cipp/settings', name: 'Settings', component: CIPPSettings }, { path: '/cipp/setup', name: 'Setup', component: Setup }, + { path: '/cipp/scheduler', name: 'Scheduler', component: Scheduler }, + { path: '/tenant/administration/gdap', name: 'GDAP Wizard', component: GDAP }, { path: '/tenant/administration/gdap-invite', name: 'GDAP Invite Wizard', component: GDAPInvite }, { diff --git a/src/components/forms/RFFComponents.js b/src/components/forms/RFFComponents.js index a360ec2fc49e..9aef7ca81c35 100644 --- a/src/components/forms/RFFComponents.js +++ b/src/components/forms/RFFComponents.js @@ -88,9 +88,10 @@ export const RFFCFormSwitch = ({ className = 'mb-3', validate, disabled = false, + initialValue, }) => { return ( - + {({ meta, input }) => ( { const dispatch = useDispatch() + const location = useLocation() const sidebarShow = useSelector((state) => state.app.sidebarShow) const currentTheme = useSelector((state) => state.app.currentTheme) const preferredTheme = useMediaPredicate('(prefers-color-scheme: dark)') ? 'impact' : 'cyberdrain' @@ -57,6 +62,16 @@ const AppHeader = () => { + + + + + + + diff --git a/src/components/tables/CellBadge.js b/src/components/tables/CellBadge.js index 5234f65e2fca..ae6b948ce83f 100644 --- a/src/components/tables/CellBadge.js +++ b/src/components/tables/CellBadge.js @@ -3,6 +3,24 @@ import React from 'react' import { CBadge } from '@coreui/react' export const CellBadge = ({ label = '', color = '', children, ...rest }) => { + //Create a case select, and return the color based on the label + switch (label.toLowerCase()) { + case 'planned': + color = 'info' + break + case 'failed': + color = 'danger' + break + case 'completed': + color = 'success' + break + case 'banned': + color = 'danger' + break + case 'running': + color = 'primary' + } + return ( {label} diff --git a/src/components/tables/CellDate.js b/src/components/tables/CellDate.js index 9806f137c290..04f7bf9c5f3d 100644 --- a/src/components/tables/CellDate.js +++ b/src/components/tables/CellDate.js @@ -4,13 +4,6 @@ import PropTypes from 'prop-types' import { CTooltip } from '@coreui/react' import ReactTimeAgo from 'react-time-ago' -/** - * - * @param format ['short', 'long', 'relative'] - * @param value - * @returns {JSX.Element} - * @constructor - */ export const CellDate = ({ format = 'short', showTime = true, showDate = true, cell }) => { if (!cell || (!showTime && !showDate)) { return
@@ -21,25 +14,29 @@ export const CellDate = ({ format = 'short', showTime = true, showDate = true, c locale = navigator.language } - // cheatsheet - // https://devhints.io/wip/intl-datetime - const dateTimeArgs = [ - [locale, 'default'], // add fallback option if locale doesn't load properly - ] + // Convert cell value to a number and check if it's a Unix timestamp + const possibleUnixTimestamp = Number(cell) + const isUnixTimestamp = !isNaN(possibleUnixTimestamp) && possibleUnixTimestamp > 1000000000 + let dateObject + + if (isUnixTimestamp) { + dateObject = moment.unix(possibleUnixTimestamp).toDate() + } else { + dateObject = moment(cell).toDate() + } + + const dateTimeArgs = [[locale, 'default']] const dateTimeFormatOptions = {} - if (format == 'relative') { + + if (format === 'relative') { try { - return ( - - - - ) + return } catch (error) { - console.error('Error formatting date, fallback to string value', { date: cell, error }) + console.error('Error formatting date, fallback to string value', { date: dateObject, error }) return ( - -
{String(cell)}
+ +
{String(dateObject)}
) } @@ -56,16 +53,14 @@ export const CellDate = ({ format = 'short', showTime = true, showDate = true, c let formatted try { - // lots of dates returned are unreliably parsable (e.g. non ISO8601 format) - // fallback using moment to parse into date object - formatted = new Intl.DateTimeFormat(...dateTimeArgs).format(moment(cell).toDate()) + formatted = new Intl.DateTimeFormat(...dateTimeArgs).format(dateObject) } catch (error) { - console.error('Error formatting date, fallback to string value', { date: cell, error }) - formatted = cell + console.error('Error formatting date, fallback to string value', { date: dateObject, error }) + formatted = dateObject.toString() } return ( - +
{String(formatted)}
) @@ -74,7 +69,7 @@ export const CellDate = ({ format = 'short', showTime = true, showDate = true, c CellDate.propTypes = { format: PropTypes.oneOf(['short', 'medium', 'long', 'full', 'relative']), - cell: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]), + cell: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.instanceOf(Date)]), showTime: PropTypes.bool, showDate: PropTypes.bool, } diff --git a/src/components/tables/CippDatatable.js b/src/components/tables/CippDatatable.js index d41dc8e342ed..e36befb3dea3 100644 --- a/src/components/tables/CippDatatable.js +++ b/src/components/tables/CippDatatable.js @@ -13,30 +13,23 @@ export default function CippDatatable({ path, params, ...rest }) { isFetching, error, } = useListDatatableQuery({ path, params: { refreshGuid, $filter: graphFilter, ...params } }) + + var defaultFilterText = '' + if (params?.Parameters?.$filter) { + defaultFilterText = 'Graph: ' + params?.Parameters?.$filter + } return ( <> - {data[0]?.Queued ? ( - <> - {data[0]?.QueueMessage} - - - ) : ( - - )} + {data[0]?.Queued && {data[0]?.QueueMessage}} + ) } diff --git a/src/components/tables/CippTable.js b/src/components/tables/CippTable.js index fa11d257ea78..288685f43f1a 100644 --- a/src/components/tables/CippTable.js +++ b/src/components/tables/CippTable.js @@ -1,4 +1,4 @@ -import React, { useRef } from 'react' +import React, { useRef, useMemo, useState, useCallback } from 'react' import { useSelector } from 'react-redux' import { ExportCsvButton, ExportPDFButton } from 'src/components/buttons' import { @@ -25,16 +25,10 @@ import { cellGenericFormatter } from './CellGenericFormat' import { ModalService } from '../utilities' import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' import { ConfirmModal } from '../utilities/SharedModal' -import { useState } from 'react' +import { debounce } from 'lodash' +import { useSearchParams } from 'react-router-dom' -const FilterComponent = ({ - filterText, - onFilter, - onClear, - filterlist, - onFilterPreset, - onFilterGraph, -}) => ( +const FilterComponent = ({ filterText, onFilter, onClear, filterlist, onFilterPreset }) => ( <> @@ -50,26 +44,17 @@ const FilterComponent = ({ { onFilterPreset('') - onFilterGraph('') }} > Clear Filter {filterlist && filterlist.map((item, idx) => { - if (item.hasOwnProperty('graphFilter') && item.graphFilter == true) { - return ( - onFilterGraph(item.filter)}> - {item.filterName} - - ) - } else { - return ( - onFilterPreset(item.filter)}> - {item.filterName} - - ) - } + return ( + onFilterPreset(item.filter)}> + {item.filterName} + + ) })} @@ -93,7 +78,6 @@ FilterComponent.propTypes = { onClear: PropTypes.func, filterlist: PropTypes.arrayOf(PropTypes.object), onFilterPreset: PropTypes.func, - onFilterGraph: PropTypes.func, } const customSort = (rows, selector, direction) => { @@ -127,6 +111,8 @@ export default function CippTable({ graphFilterFunction = null, columns = [], dynamicColumns = true, + defaultFilterText = '', + isModal = false, filterlist, tableProps: { keyField = 'id', @@ -153,13 +139,21 @@ export default function CippTable({ const inputRef = useRef('') const [loopRunning, setLoopRunning] = React.useState(false) const [massResults, setMassResults] = React.useState([]) - const [filterText, setFilterText] = React.useState('') + const [filterText, setFilterText] = React.useState(defaultFilterText) + const [filterviaURL, setFilterviaURL] = React.useState(false) const [updatedColumns, setUpdatedColumns] = React.useState(columns) const [selectedRows, setSelectedRows] = React.useState(false) const [genericGetRequest, getResults] = useLazyGenericGetRequestQuery() const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() const [getDrowndownInfo, dropDownInfo] = useLazyGenericGetRequestQuery() const [modalContent, setModalContent] = useState(null) + //get the search params called "tableFilter" and set the filter to that. + const [searchParams, setSearchParams] = useSearchParams() + if (searchParams.get('tableFilter') && !filterviaURL && !isModal) { + setFilterText(searchParams.get('tableFilter')) + setFilterviaURL(true) + } + useEffect(() => { if (dropDownInfo.isFetching) { handleModal( @@ -199,22 +193,99 @@ export default function CippTable({ } } const [resetPaginationToggle, setResetPaginationToggle] = React.useState(false) - const filteredItems = Array.isArray(data) - ? data.filter( - (item) => JSON.stringify(item).toLowerCase().indexOf(filterText.toLowerCase()) !== -1, - ) - : [] - - const applyFilter = (e) => { - setFilterText(e.target.value) + // Helper function to escape special characters in a string for regex + function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } const setGraphFilter = (e) => { if (graphFilterFunction) { graphFilterFunction(e) + console.log(e) + } + } + + const debounceSetGraphFilter = useMemo(() => { + return debounce(setGraphFilter, 1000) + }, []) + + const debounceSetSearchParams = useCallback(() => { + const currentUrl = new URL(window.location.href) + if (filterText !== '') { + currentUrl.searchParams.set('tableFilter', filterText) + window.history.replaceState({}, '', currentUrl.toString()) + } else { + currentUrl.searchParams.delete('tableFilter') + window.history.replaceState({}, '', currentUrl.toString()) + } + }, [filterText]) + + const filterData = (data, filterText) => { + if (!isModal) { + const debouncedSetSearchParams = debounce(debounceSetSearchParams, 1000) + debouncedSetSearchParams() + } + if (filterText.startsWith('Graph:')) { + var query = filterText.slice(6).trim() + debounceSetGraphFilter(query) + return data + } else if (filterText.startsWith('Complex:')) { + const conditions = filterText.slice(9).split(';') + + return conditions.reduce((filteredData, condition) => { + const match = condition.trim().match(/(\w+)\s*(eq|ne|like|notlike|gt|lt)\s*(.+)/) + + if (!match) { + return filteredData // Keep the current filtered data as is + } + + let [property, operator, value] = match.slice(1) + value = escapeRegExp(value) // Escape special characters + + return filteredData.filter((item) => { + // Find the actual key in the item that matches the property (case insensitive) + const actualKey = Object.keys(item).find( + (key) => key.toLowerCase() === property.toLowerCase(), + ) + + if (!actualKey) { + //set the error message so the user understands the key is not found. + console.error(`FilterError: Property "${property}" not found.`) + return false // Keep the item if the property is not found + } else { + } + + switch (operator) { + case 'eq': + return String(item[actualKey]).toLowerCase() === value.toLowerCase() + case 'ne': + return String(item[actualKey]).toLowerCase() !== value.toLowerCase() + case 'like': + return String(item[actualKey]).toLowerCase().includes(value.toLowerCase()) + case 'notlike': + return !String(item[actualKey]).toLowerCase().includes(value.toLowerCase()) + case 'gt': + return parseFloat(item[actualKey]) > parseFloat(value) + case 'lt': + return parseFloat(item[actualKey]) < parseFloat(value) + default: + return true + } + }) + }, data) + } else { + return data.filter( + (item) => JSON.stringify(item).toLowerCase().indexOf(filterText.toLowerCase()) !== -1, + ) } } + const filteredItems = Array.isArray(data) ? filterData(data, filterText) : [] + + const applyFilter = (e) => { + setFilterText(e.target.value) + } + useEffect(() => { if (columns !== updatedColumns) { setUpdatedColumns(columns) @@ -539,11 +610,6 @@ export default function CippTable({ onFilter={(e) => setFilterText(e.target.value)} onFilterPreset={(e) => { setFilterText(e) - setGraphFilter('') - }} - onFilterGraph={(e) => { - setFilterText('') - setGraphFilter(e) }} onClear={handleClear} filterText={filterText} @@ -567,65 +633,61 @@ export default function CippTable({ const tablePageSize = useSelector((state) => state.app.tablePageSize) return (
- {!isFetching && error && Error loading data} - {!error && ( -
- {(columns.length === updatedColumns.length || !dynamicColumns) && ( - <> - {(massResults.length >= 1 || loopRunning) && ( - - {massResults.map((message, idx) => { - const results = message.data?.Results - const displayResults = Array.isArray(results) ? results.join(', ') : results + {!isFetching && error && Error loading data} +
+ {(columns.length === updatedColumns.length || !dynamicColumns) && ( + <> + {(massResults.length >= 1 || loopRunning) && ( + + {massResults.map((message, idx) => { + const results = message.data?.Results + const displayResults = Array.isArray(results) ? results.join(', ') : results - return
  • {displayResults}
  • - })} - {loopRunning && ( -
  • - -
  • - )} -
    - )} - } - paginationRowsPerPageOptions={[25, 50, 100, 200, 500]} - {...rest} - /> - {selectedRows.length >= 1 && ( - Selected {selectedRows.length} items - )} - - )} -
    - )} + return
  • {displayResults}
  • + })} + {loopRunning && ( +
  • + +
  • + )} +
    + )} + } + paginationRowsPerPageOptions={[25, 50, 100, 200, 500]} + {...rest} + /> + {selectedRows.length >= 1 && Selected {selectedRows.length} items} + + )} +
    ) } diff --git a/src/components/utilities/CippCodeOffcanvas.js b/src/components/utilities/CippCodeOffcanvas.js index c8cdbf175d35..38a23b81663f 100644 --- a/src/components/utilities/CippCodeOffcanvas.js +++ b/src/components/utilities/CippCodeOffcanvas.js @@ -6,7 +6,14 @@ import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 's import { Editor } from '@monaco-editor/react' import { useSelector } from 'react-redux' -function CippCodeOffCanvas({ row, state, hideFunction, type }) { +function CippCodeOffCanvas({ + row, + state, + hideFunction, + type, + title = 'Template JSON', + hideButton = false, +}) { const [SaveTemplate, templateDetails] = useLazyGenericPostRequestQuery() const currentTheme = useSelector((state) => state.app.currentTheme) const [templateData, setFormData] = useState(row) @@ -23,7 +30,7 @@ function CippCodeOffCanvas({ row, state, hideFunction, type }) { return ( <> - - SaveTemplate({ - path: `/api/ExecEditTemplate?type=${type}`, - method: 'POST', - values: templateData, - }) - } - > - Save changes {templateDetails.isFetching && } - + {!hideButton && ( + + SaveTemplate({ + path: `/api/ExecEditTemplate?type=${type}`, + method: 'POST', + values: templateData, + }) + } + > + Save changes {templateDetails.isFetching && } + + )} {templateDetails.isSuccess && !templateDetails.isFetching && ( diff --git a/src/components/utilities/CippFuzzySearch.js b/src/components/utilities/CippFuzzySearch.js index a97625dcd818..43021b80014e 100644 --- a/src/components/utilities/CippFuzzySearch.js +++ b/src/components/utilities/CippFuzzySearch.js @@ -13,7 +13,6 @@ function CippfuzzySearch(options) { useExtendedSearch: true, includeMatches: true, includeScore: true, - useExtendedSearch: true, }) return function (value) { if (!value.length) { diff --git a/src/components/utilities/SharedModal.js b/src/components/utilities/SharedModal.js index 47fb57f2f547..4975c0cc17f6 100644 --- a/src/components/utilities/SharedModal.js +++ b/src/components/utilities/SharedModal.js @@ -13,7 +13,7 @@ import { CippTable } from 'src/components/tables' function mapBodyComponent({ componentType, data, componentProps }) { switch (componentType) { case 'table': - return + return case 'list': return
    {Array.isArray(data) && data.map((el, idx) =>
    {el}
    )}
    case 'text': diff --git a/src/data/Extensions.json b/src/data/Extensions.json index cb15aa1043e8..3c1a1e7f1cd8 100644 --- a/src/data/Extensions.json +++ b/src/data/Extensions.json @@ -4,7 +4,7 @@ "type": "CIPP-API", "cat": "API", "forceSyncButton": false, - "helpText": "This integration allows you to use to enable API access outside of CIPP usage. Requires Global Administrator permissions inside your tenant for activation of the API. These credentials will only be shown once.", + "helpText": "This integration allows you to enable CIPP-API access outside of CIPP. Requires Global Administrator permissions inside your tenant for activation of the API. The API credentials will only be shown once.", "SettingOptions": [ { "type": "checkbox", diff --git a/src/views/cipp/CIPPSettings.js b/src/views/cipp/CIPPSettings.js index 09d06f708953..7ba3cb285ad7 100644 --- a/src/views/cipp/CIPPSettings.js +++ b/src/views/cipp/CIPPSettings.js @@ -1069,6 +1069,7 @@ const NotificationsSettings = () => {
    true} initialValues={{ ...notificationListResult.data, logsToInclude: notificationListResult.data?.logsToInclude?.map((m) => ({ @@ -1576,58 +1577,61 @@ const ExtensionsTab = () => { } const MappingsTab = () => { - const [listBackend, listBackendResult] = useLazyGenericGetRequestQuery() - const [setExtensionconfig, extensionConfigResult] = useLazyGenericPostRequestQuery() + const [listHaloBackend, listBackendHaloResult = []] = useLazyGenericGetRequestQuery() + const [setHaloExtensionconfig, extensionHaloConfigResult = []] = useLazyGenericPostRequestQuery() - const onSubmit = (values) => { - setExtensionconfig({ - path: 'api/ExecExtensionMapping?AddMapping=true', + const onHaloSubmit = (values) => { + setHaloExtensionconfig({ + path: 'api/ExecExtensionMapping?AddMapping=Halo', values: { mappings: values }, }) } return (
    - {listBackendResult.isUninitialized && - listBackend({ path: 'api/ExecExtensionMapping?List=true' })} + {listBackendHaloResult.isUninitialized && + listHaloBackend({ path: 'api/ExecExtensionMapping?List=Halo' })} <> - + HaloPSA Mapping Table - {listBackendResult.isFetching ? ( + {listBackendHaloResult.isFetching ? ( ) : ( { return ( Use the table below to map your client to the correct PSA client - {listBackendResult.isSuccess && - listBackendResult.data.Tenants.map((tenant) => ( + {listBackendHaloResult.isSuccess && + listBackendHaloResult.data.Tenants?.map((tenant) => ( ))} - {extensionConfigResult.isFetching && ( + {extensionHaloConfigResult.isFetching && ( )} Set Mappings - {(extensionConfigResult.isSuccess || extensionConfigResult.isError) && ( - - {extensionConfigResult.isSuccess - ? extensionConfigResult.data.Results + {(extensionHaloConfigResult.isSuccess || + extensionHaloConfigResult.isError) && ( + + {extensionHaloConfigResult.isSuccess + ? extensionHaloConfigResult.data.Results : 'Error'} )} diff --git a/src/views/cipp/Scheduler.js b/src/views/cipp/Scheduler.js new file mode 100644 index 000000000000..8483c2c477b2 --- /dev/null +++ b/src/views/cipp/Scheduler.js @@ -0,0 +1,384 @@ +import React, { useEffect, useState } from 'react' +import { CButton, CCallout, CCol, CForm, CFormLabel, CRow, CSpinner, CTooltip } from '@coreui/react' +import useQuery from 'src/hooks/useQuery' +import { useDispatch, useSelector } from 'react-redux' +import { Field, Form, FormSpy } from 'react-final-form' +import { + Condition, + RFFCFormCheck, + RFFCFormInput, + RFFCFormRadio, + RFFCFormSwitch, + RFFCFormTextarea, + RFFSelectSearch, +} from 'src/components/forms' +import countryList from 'src/data/countryList' + +import { + useGenericGetRequestQuery, + useLazyGenericGetRequestQuery, + useLazyGenericPostRequestQuery, +} from 'src/store/api/app' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCircleNotch, faEdit, faEye } from '@fortawesome/free-solid-svg-icons' +import { CippContentCard, CippPage, CippPageList } from 'src/components/layout' +import { password } from 'src/validators' +import { + CellDate, + CellDelegatedPrivilege, + cellBadgeFormatter, + cellBooleanFormatter, + cellDateFormatter, +} from 'src/components/tables' +import { CellTip, cellGenericFormatter } from 'src/components/tables/CellGenericFormat' +import DatePicker from 'react-datepicker' +import 'react-datepicker/dist/react-datepicker.css' +import TenantListSelector from 'src/components/utilities/TenantListSelector' +import { ModalService, TenantSelector } from 'src/components/utilities' +import CippCodeOffCanvas from 'src/components/utilities/CippCodeOffcanvas' + +const Offcanvas = (row, rowIndex, formatExtraData) => { + const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery() + const [ocVisible, setOCVisible] = useState(false) + + const handleDeleteSchedule = (apiurl, message) => { + ModalService.confirm({ + title: 'Confirm', + body:
    {message}
    , + onConfirm: () => ExecuteGetRequest({ path: apiurl }), + confirmLabel: 'Continue', + cancelLabel: 'Cancel', + }) + } + let jsonResults + try { + jsonResults = JSON.parse(row.Results) + } catch (error) { + jsonResults = row.Results + } + + return ( + <> + + setOCVisible(true)}> + + + + + + handleDeleteSchedule( + `/api/RemoveScheduledItem?&ID=${row.RowKey}`, + 'Do you want to delete this job?', + ) + } + size="sm" + variant="ghost" + color="danger" + > + + + + setOCVisible(false)} + /> + + ) +} + +const Scheduler = () => { + const currentDate = new Date() + const [startDate, setStartDate] = useState(currentDate) + const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) + const [refreshState, setRefreshState] = useState(false) + const taskName = `Scheduled Task ${currentDate.toLocaleString()}` + const { data: availableCommands = [], isLoading: isLoadingcmd } = useGenericGetRequestQuery({ + path: 'api/ListFunctionParameters?Module=CIPPCore', + }) + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + const onSubmit = (values) => { + const unixTime = Math.floor(startDate.getTime() / 1000) + const shippedValues = { + TenantFilter: tenantDomain, + Name: values.taskName, + Command: values.command, + Parameters: values.parameters, + ScheduledTime: unixTime, + Recurrence: values.Recurrence, + PostExecution: { + Webhook: values.webhook, + Email: values.email, + PSA: values.psa, + }, + } + genericPostRequest({ path: '/api/AddScheduledItem', values: shippedValues }).then((res) => { + setRefreshState(res.requestId) + }) + } + + const columns = [ + { + name: 'Name', + selector: (row) => row['Name'], + sortable: true, + cell: (row) => CellTip(row['Name']), + exportSelector: 'Name', + }, + { + name: 'Tenant', + selector: (row) => row['Tenant'], + sortable: true, + cell: (row) => CellTip(row['Tenant']), + exportSelector: 'Tenant', + }, + { + name: 'Task State', + selector: (row) => row['TaskState'], + sortable: true, + cell: cellBadgeFormatter(), + exportSelector: 'TaskState', + }, + { + name: 'Command', + selector: (row) => row['Command'], + sortable: true, + cell: (row) => CellTip(row['Command']), + exportSelector: 'Command', + }, + { + name: 'Parameters', + selector: (row) => row['Parameters'], + sortable: true, + cell: (row) => CellTip(row['Parameters']), + exportSelector: 'Parameters', + }, + { + name: 'Scheduled Time', + selector: (row) => row['ScheduledTime'], + sortable: true, + cell: cellDateFormatter({ format: 'relative' }), + exportSelector: 'ScheduledTime', + }, + { + name: 'Last executed time', + selector: (row) => row['ExecutedTime'], + sortable: true, + cell: cellDateFormatter({ format: 'relative' }), + exportSelector: 'ExecutedTime', + }, + { + name: 'Recurrence', + selector: (row) => row['Recurrence'], + sortable: true, + cell: (row) => CellTip(row['Recurrence']), + exportSelector: 'Recurrence', + }, + { + name: 'Sending to', + selector: (row) => row['PostExecution'], + sortable: true, + cell: (row) => CellTip(row['PostExecution']), + exportSelector: 'PostExecution', + }, + { + name: 'Actions', + cell: Offcanvas, + maxWidth: '80px', + }, + ] + return ( + + <> + + + + true} + render={({ handleSubmit, submitting, values }) => { + return ( + + + + + {(props) => } + + + + + + + + + + + setStartDate(date)} + /> + + + + + + + + + + ({ + value: cmd.Function, + name: cmd.Function, + }))} + name="command" + placeholder={ + isLoadingcmd ? ( + + ) : ( + 'Select a command or report to execute.' + ) + } + label="Command to execute" + /> + + + + + {(props) => { + const selectedCommand = availableCommands.find( + (cmd) => cmd.Function === props.values.command?.value, + ) + let paramblock = null + if (selectedCommand) { + //if the command parameter type is boolean we use else . + const parameters = selectedCommand.Parameters + if (parameters.length > 0) { + paramblock = parameters.map((param, idx) => ( + + + {param.Type === 'System.Boolean' ? ( + <> + + + + ) : ( + + )} + + + )) + } + } + return paramblock + }} + + + + + + + + + + + + + + + Add Schedule + {postResults.isFetching && ( + + )} + + + + {postResults.isSuccess && ( + +
  • {postResults.data.Results}
  • +
    + )} +
    + ) + }} + /> +
    +
    + + + +
    + +
    + ) +} + +export default Scheduler diff --git a/src/views/email-exchange/administration/EditMailboxPermissions.js b/src/views/email-exchange/administration/EditMailboxPermissions.js index 2841cf1c4d41..b0649954994b 100644 --- a/src/views/email-exchange/administration/EditMailboxPermissions.js +++ b/src/views/email-exchange/administration/EditMailboxPermissions.js @@ -16,7 +16,6 @@ import { CSpinner, } from '@coreui/react' import useQuery from 'src/hooks/useQuery' -import { CippPage, CippPageList, CippMasonry, CippMasonryItem } from 'src/components/layout' import { useDispatch } from 'react-redux' import { Form, Field } from 'react-final-form' import { RFFSelectSearch, RFFCFormSelect, RFFCFormCheck, RFFCFormInput } from 'src/components/forms' @@ -38,38 +37,131 @@ const formatter = (cell, warning = false, reverse = false, colourless = false) = CellBoolean({ cell, warning, reverse, colourless }) const MailboxSettings = () => { + const dispatch = useDispatch() + let query = useQuery() + const userId = query.get('userId') + const tenantDomain = query.get('tenantDomain') const [active, setActive] = useState(1) + const { + data: usercal = {}, + isFetching: usercalIsFetching, + error: usercalError, + } = useListCalendarPermissionsQuery({ tenantDomain, userId }) + const columnsCal = [ + { + name: 'User', + selector: (row) => row['User'], + sortable: true, + wrap: true, + cell: (row) => row['User'], + exportSelector: 'User', + maxWidth: '150px', + }, + { + name: 'AccessRights', + selector: (row) => row['AccessRights'], + sortable: true, + wrap: true, + cell: (row) => row['AccessRights'], + exportSelector: 'AccessRights', + maxWidth: '150px', + }, + { + name: 'Identity', + selector: (row) => row['Identity'], + sortable: true, + wrap: true, + cell: (row) => row['Identity'], + exportSelector: 'Identity', + maxWidth: '150px', + }, + ] + const columns = [ + { + name: 'User', + selector: (row) => row.User, + sortable: true, + wrap: true, + exportSelector: 'User', + }, + { + name: 'Permissions', + selector: (row) => row['Permissions'], + sortable: true, + wrap: true, + exportSelector: 'Permissions', + }, + ] + + const { + data: user = {}, + isFetching: userIsFetching, + error: userError, + } = useListMailboxPermissionsQuery({ tenantDomain, userId }) + return ( - - - - - setActive(1)} href="#"> - Mailbox Permissions - - setActive(2)} href="#"> - Calendar Permissions - - setActive(3)} href="#"> - Mailbox Forwarding - - - - - - - - - - - - - - - - - - + + + + + + setActive(1)} href="#"> + Mailbox Permissions + + setActive(2)} href="#"> + Calendar Permissions + + setActive(3)} href="#"> + Mailbox Forwarding + + + + + + + + + + + + + + + + + + + + + + Account Information - {userId} + + + {active === 1 && ( + <> + {userIsFetching && } + {!userIsFetching && ( + + )} + + )} + {active === 2 && ( + <> + {usercalIsFetching && } + {!usercalIsFetching && ( + + )} + + )} + {active === 3 && ( + <> + + + )} + + + + ) } @@ -86,13 +178,6 @@ const MailboxPermissions = () => { //const [EditMailboxPermission, { error: EditMailboxPermissionError, isFetching: EditMailboxPermissionIsFetching }] = useEditMailboxPermissionMutation() const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() - const { - data: user = {}, - isFetching: userIsFetching, - error: userError, - refetch: refetchPermissions, - } = useListMailboxPermissionsQuery({ tenantDomain, userId }) - const { data: users = [], isFetching: usersIsFetching, @@ -100,9 +185,6 @@ const MailboxPermissions = () => { } = useListUsersQuery({ tenantDomain }) useEffect(() => { - if (postResults.isSuccess) { - refetchPermissions() - } if (!userId || !tenantDomain) { ModalService.open({ body: 'Error invalid request, could not load requested user.', @@ -112,7 +194,7 @@ const MailboxPermissions = () => { } else { setQueryError(false) } - }, [userId, tenantDomain, dispatch, postResults, refetchPermissions]) + }, [userId, tenantDomain, dispatch, postResults]) const onSubmit = (values) => { const shippedValues = { userid: userId, @@ -128,36 +210,16 @@ const MailboxPermissions = () => { //window.alert(JSON.stringify(shippedValues)) genericPostRequest({ path: '/api/ExecEditMailboxPermissions', values: shippedValues }) } - const initialState = { - ...user, - } - - const columns = [ - { - name: 'User', - selector: (row) => row.User, - sortable: true, - wrap: true, - exportSelector: 'User', - }, - { - name: 'Permissions', - selector: (row) => row['Permissions'], - sortable: true, - wrap: true, - exportSelector: 'Permissions', - }, - ] const formDisabled = queryError === true return ( - + <> {!queryError && ( <> {queryError && ( - + {/* @todo add more descriptive help message here */} Failed to load user @@ -166,203 +228,147 @@ const MailboxPermissions = () => { )} - - - - Account Details - {userId} - - - {usersIsFetching && } - {userError && Error loading user} - {!usersIsFetching && ( - { - return ( - - - - ({ - value: user.mail, - name: `${user.displayName} - ${user.mail} `, - }))} - placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} - name="RemoveFullAccess" - /> - {usersError && Failed to load list of users} - - - ({ - value: user.mail, - name: `${user.displayName} - ${user.mail} `, - }))} - placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} - name="AddFullAccess" - /> - {usersError && Failed to load list of users} - - - ({ - value: user.mail, - name: `${user.displayName} - ${user.mail} `, - }))} - placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} - name="AddFullAccessNoAutoMap" - /> - {usersError && Failed to load list of users} - - - ({ - value: user.mail, - name: `${user.displayName} - ${user.mail} `, - }))} - placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} - name="AddSendAs" - /> - {usersError && Failed to load list of users} - - - ({ - value: user.mail, - name: `${user.displayName} - ${user.mail} `, - }))} - placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} - name="RemoveSendAs" - /> - {usersError && Failed to load list of users} - - - ({ - value: user.mail, - name: `${user.displayName} - ${user.mail} `, - }))} - placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} - name="AddSendOnBehalf" - /> - {usersError && Failed to load list of users} - - - ({ - value: user.mail, - name: `${user.displayName} - ${user.mail} `, - }))} - placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} - name="RemoveSendOnBehalf" - /> - {usersError && Failed to load list of users} - - - - - - Edit User Permissions - {postResults.isFetching && ( - - )} - - - - {postResults.isSuccess && ( - - {postResults.data.Results.map((result, idx) => ( -
  • {result}
  • - ))} -
    + {usersIsFetching && } + {!usersIsFetching && ( + { + return ( + + + + ({ + value: user.mail, + name: `${user.displayName} - ${user.mail} `, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="RemoveFullAccess" + /> + {usersError && Failed to load list of users} + + + ({ + value: user.mail, + name: `${user.displayName} - ${user.mail} `, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="AddFullAccess" + /> + {usersError && Failed to load list of users} + + + ({ + value: user.mail, + name: `${user.displayName} - ${user.mail} `, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="AddFullAccessNoAutoMap" + /> + {usersError && Failed to load list of users} + + + ({ + value: user.mail, + name: `${user.displayName} - ${user.mail} `, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="AddSendAs" + /> + {usersError && Failed to load list of users} + + + ({ + value: user.mail, + name: `${user.displayName} - ${user.mail} `, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="RemoveSendAs" + /> + {usersError && Failed to load list of users} + + + ({ + value: user.mail, + name: `${user.displayName} - ${user.mail} `, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="AddSendOnBehalf" + /> + {usersError && Failed to load list of users} + + + ({ + value: user.mail, + name: `${user.displayName} - ${user.mail} `, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="RemoveSendOnBehalf" + /> + {usersError && Failed to load list of users} + + + + + + Edit User Permissions + {postResults.isFetching && ( + )} - - ) - }} - /> - )} -
    -
    -
    - - - - Account Information - - - {userIsFetching && } - {!userIsFetching && ( - <> - - - )} - - - + +
    + + {postResults.isSuccess && ( + + {postResults.data.Results.map((result, idx) => ( +
  • {result}
  • + ))} +
    + )} +
    + ) + }} + /> + )} )} -
    + ) } -const columns = [ - { - name: 'User', - selector: (row) => row['User'], - sortable: true, - wrap: true, - cell: (row) => row['User'], - exportSelector: 'User', - maxWidth: '150px', - }, - { - name: 'AccessRights', - selector: (row) => row['AccessRights'], - sortable: true, - wrap: true, - cell: (row) => row['AccessRights'], - exportSelector: 'AccessRights', - maxWidth: '150px', - }, - { - name: 'Identity', - selector: (row) => row['Identity'], - sortable: true, - wrap: true, - cell: (row) => row['Identity'], - exportSelector: 'Identity', - maxWidth: '150px', - }, -] - const CalendarPermissions = () => { const dispatch = useDispatch() let query = useQuery() @@ -419,7 +425,7 @@ const CalendarPermissions = () => { UsersMapped.unshift({ value: 'Default', name: 'Default' }) return ( - + <> {!queryError && ( <> {postResults.isSuccess && ( @@ -436,109 +442,85 @@ const CalendarPermissions = () => { )} - - - - Account Details - - - {userIsFetching && } - {userError && Error loading user} - {!userIsFetching && ( - { - return ( - - - - - {usersError && Failed to load list of users} - - - - {usersError && Failed to load list of users} - - - + {userIsFetching && } + {userError && Error loading user} + {!userIsFetching && ( + { + return ( + + + + + {usersError && Failed to load list of users} + + + + {usersError && Failed to load list of users} + + + + {usersError && Failed to load list of users} + + + + + + Edit Permissions + {postResults.isFetching && ( + - {usersError && Failed to load list of users} - - - - - - Edit Permissions - {postResults.isFetching && ( - - )} - - - - {postResults.isSuccess && ( - {postResults.data?.Results} - )} - - ) - }} - /> - )} - - - - - - - Current Permissions - - - {userIsFetching && } - {!userIsFetching && ( - <> - {user.length > 0 && ( - - )} - - )} - - + )} + + + + {postResults.isSuccess && ( + {postResults.data?.Results} + )} + + ) + }} + /> + )} )} - + ) } @@ -557,7 +539,6 @@ const MailboxForwarding = () => { data: user = {}, isFetching: userIsFetching, error: userError, - refetch: refetchPermissions, } = useListMailboxPermissionsQuery({ tenantDomain, userId }) const { @@ -568,7 +549,6 @@ const MailboxForwarding = () => { useEffect(() => { if (postResults.isSuccess) { - refetchPermissions() } if (!userId || !tenantDomain) { ModalService.open({ @@ -579,7 +559,7 @@ const MailboxForwarding = () => { } else { setQueryError(false) } - }, [userId, tenantDomain, dispatch, postResults, refetchPermissions]) + }, [userId, tenantDomain, dispatch, postResults]) const onSubmit = (values) => { const shippedValues = { userid: userId, @@ -616,7 +596,7 @@ const MailboxForwarding = () => { const formDisabled = queryError === true return ( - + <> {!queryError && ( <> {queryError && ( @@ -630,139 +610,122 @@ const MailboxForwarding = () => { )} - - - - Account Details - {userId} - - - {usersIsFetching && } - {userError && Error loading user} - {!usersIsFetching && ( - { - return ( - - - -
    - -
    - {values.forwardOption === 'internalAddress' && ( - ({ - value: user.mail, - name: `${user.displayName} - ${user.mail} `, - }))} - placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} - name="ForwardInternal" - /> - )} - {usersError && Failed to load list of users} -
    -
    - - -
    - -
    - {values.forwardOption === 'ExternalAddress' && ( - - )} -
    -
    - - -
    - -
    -
    -
    - - - - - Edit Forwarding - {postResults.isFetching && ( - - )} - - - - {postResults.isSuccess && ( - - {postResults.data.Results.map((result, idx) => ( -
  • {result}
  • - ))} -
    + + {usersIsFetching && } + {userError && Error loading user} + {!usersIsFetching && ( + { + return ( + + + +
    + +
    + {values.forwardOption === 'internalAddress' && ( + ({ + value: user.mail, + name: `${user.displayName} - ${user.mail} `, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="ForwardInternal" + /> )} -
    - ) - }} - /> - )} -
    -
    -
    - - - - Account Details - {userId} - - - - - + {usersError && Failed to load list of users} + +
    + + +
    + +
    + {values.forwardOption === 'ExternalAddress' && ( + + )} +
    +
    + + +
    + +
    +
    +
    + + + + + Edit Forwarding + {postResults.isFetching && ( + + )} + + + + {postResults.isSuccess && ( + + {postResults.data.Results.map((result, idx) => ( +
  • {result}
  • + ))} +
    + )} + + ) + }} + /> + )} )} -
    + ) } @@ -784,14 +747,17 @@ const ForwardingSettings = () => { return ( - - {content.map((item, index) => ( -
    -
    {item.heading}
    -

    {item.body}

    -
    - ))} -
    + {isFetching && } + {!isFetching && ( + + {content.map((item, index) => ( +
    +
    {item.heading}
    +

    {item.body}

    +
    + ))} +
    + )}
    ) } diff --git a/src/views/email-exchange/spamfilter/DeploySpamfilter.js b/src/views/email-exchange/spamfilter/DeploySpamfilter.js index 8c3a470efb20..f798ebf48003 100644 --- a/src/views/email-exchange/spamfilter/DeploySpamfilter.js +++ b/src/views/email-exchange/spamfilter/DeploySpamfilter.js @@ -140,7 +140,7 @@ const SpamFilterAdd = () => { { + const [visible, setVisible] = useState(false) + const currentTenant = useSelector((state) => state.app.currentTenant) const { data: organization, @@ -62,7 +65,7 @@ const Home = () => { }) const { - data: standards, + data: standards = [], isLoading: isLoadingStandards, isSuccess: issuccessStandards, isFetching: isFetchingStandards, @@ -124,6 +127,21 @@ const Home = () => { icon: faUserFriends, }, ] + + const filteredStandards = standards + .filter( + (p) => p.displayName === 'AllTenants' || p.displayName === currentTenant.defaultDomainName, + ) + .flatMap((tenant) => { + return Object.keys(tenant.standards).map((standard) => { + const standardDisplayname = allStandardsList.filter((p) => p.name.includes(standard)) + return ( +
  • + {standardDisplayname[0]?.label} ({tenant.displayName}) +
  • + ) + }) + }) return ( <> @@ -143,6 +161,10 @@ const Home = () => { +
    {issuccessUserCounts && !isFetchingUserCount ? dashboard?.Users : }
    @@ -150,6 +172,14 @@ const Home = () => {
    +
    {issuccessUserCounts && !isFetchingUserCount ? dashboard?.LicUsers : }
    @@ -157,6 +187,15 @@ const Home = () => {
    +
    {issuccessUserCounts && !isFetchingUserCount ? dashboard?.Gas : }
    @@ -164,6 +203,14 @@ const Home = () => {
    +
    {issuccessUserCounts && !isFetchingUserCount ? dashboard?.Guests : }
    @@ -261,26 +308,25 @@ const Home = () => {

    Applied Standards

    {(isLoadingStandards || isFetchingStandards) && } - {issuccessStandards && - !isFetchingStandards && - standards - .filter( - (p) => - p.displayName == 'AllTenants' || - p.displayName == currentTenant.defaultDomainName, - ) - .flatMap((tenant) => { - return Object.keys(tenant.standards).map((standard) => { - const standardDisplayname = allStandardsList.filter((p) => - p.name.includes(standard), - ) - return ( -
  • - {standardDisplayname[0]?.label} ({tenant.displayName}) -
  • - ) - }) - })} + + {issuccessStandards && !isFetchingStandards && ( + <> + {filteredStandards.slice(0, 5)} + + {filteredStandards.length > 5 && ( + <> + {filteredStandards.slice(5)} + setVisible(!visible)} + > + {visible ? 'See less' : 'See more...'} + + + )} + + )}

    Partner Relationships

    diff --git a/src/views/identity/administration/EditGroup.js b/src/views/identity/administration/EditGroup.js index 9ea8160b396a..1c9e2d44d187 100644 --- a/src/views/identity/administration/EditGroup.js +++ b/src/views/identity/administration/EditGroup.js @@ -32,9 +32,8 @@ const EditGroup = () => { let query = useQuery() const groupId = query.get('groupId') const tenantDomain = query.get('tenantDomain') - const [queryError, setQueryError] = useState(false) - + const [refreshToken, setRefresh] = useState('') const { data: group = {}, isFetching, @@ -47,14 +46,14 @@ const EditGroup = () => { isFetching: membersisFetching, error: membersError, isSuccess: membersIsSuccess, - } = useListGroupMembersQuery({ tenantDomain, groupId }) + } = useListGroupMembersQuery({ tenantDomain, groupId, refreshToken }) const { data: owners = [], isFetching: ownersisFetching, error: ownersError, isSuccess: ownersIsSuccess, - } = useListGroupOwnersQuery({ tenantDomain, groupId }) + } = useListGroupOwnersQuery({ tenantDomain, groupId, refreshToken }) const { data: users = [], isFetching: usersIsFetching, @@ -66,6 +65,7 @@ const EditGroup = () => { isFetching: contactsIsFetching, error: contactsError, } = useListContactsQuery({ tenantDomain }) + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() const [roleInfo, setroleInfo] = React.useState([]) useEffect(() => { @@ -86,7 +86,7 @@ const EditGroup = () => { }) setroleInfo(ownerWithRole.concat(memberwithRole)) } - }, [owners, members, ownersIsSuccess, membersIsSuccess]) + }, [owners, members, ownersIsSuccess, membersIsSuccess, postResults]) useEffect(() => { if (!groupId || !tenantDomain) { @@ -97,7 +97,6 @@ const EditGroup = () => { setQueryError(true) } }, [groupId, tenantDomain, dispatch]) - const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() const onSubmit = (values) => { const shippedValues = { tenantID: tenantDomain, @@ -114,7 +113,9 @@ const EditGroup = () => { mail: group[0].mail, } //window.alert(JSON.stringify(shippedValues)) - genericPostRequest({ path: '/api/EditGroup', values: shippedValues }) + genericPostRequest({ path: '/api/EditGroup', values: shippedValues }).then((res) => { + setRefresh(postResults.requestId) + }) } const tableColumns = [ { diff --git a/src/views/identity/administration/Groups.js b/src/views/identity/administration/Groups.js index 49400c7206d9..5f29ef909853 100644 --- a/src/views/identity/administration/Groups.js +++ b/src/views/identity/administration/Groups.js @@ -161,7 +161,7 @@ const Groups = () => { label: 'Hide from Global Address List', color: 'info', modal: true, - modalUrl: `/api/ExecGroupsHideFromGAL?TenantFilter=${tenant.defaultDomainName}&ID=!mail&GroupType=!calculatedGroupType&HidefromGAL=true`, + modalUrl: `/api/ExecGroupsHideFromGAL?TenantFilter=${tenant.defaultDomainName}&ID=!id&GroupType=!calculatedGroupType&HidefromGAL=true`, modalMessage: 'Are you sure you want to hide this mailbox from the global address list? Remember this will not work if the group is AD Synched.', }, @@ -169,7 +169,7 @@ const Groups = () => { label: 'Unhide from Global Address List', color: 'info', modal: true, - modalUrl: `/api/ExecGroupsHideFromGAL?TenantFilter=${tenant.defaultDomainName}&ID=!mail&GroupType=!calculatedGroupType`, + modalUrl: `/api/ExecGroupsHideFromGAL?TenantFilter=${tenant.defaultDomainName}&ID=!id&GroupType=!calculatedGroupType`, modalMessage: 'Are you sure you want to unhide this mailbox from the global address list? Remember this will not work if the group is AD Synched.', }, @@ -177,7 +177,7 @@ const Groups = () => { label: 'Only allow messages from people inside the organisation', color: 'info', modal: true, - modalUrl: `/api/ExecGroupsDeliveryManagement?TenantFilter=${tenant.defaultDomainName}&ID=!mail&GroupType=!calculatedGroupType&OnlyAllowInternal=true`, + modalUrl: `/api/ExecGroupsDeliveryManagement?TenantFilter=${tenant.defaultDomainName}&ID=!id&GroupType=!calculatedGroupType&OnlyAllowInternal=true`, modalMessage: 'Are you sure you want to only allow messages from people inside the organisation? Remember this will not work if the group is AD Synched.', }, @@ -185,7 +185,7 @@ const Groups = () => { label: 'Allow messages from people inside and outside the organisation', color: 'info', modal: true, - modalUrl: `/api/ExecGroupsDeliveryManagement?TenantFilter=${tenant.defaultDomainName}&ID=!mail&GroupType=!calculatedGroupType`, + modalUrl: `/api/ExecGroupsDeliveryManagement?TenantFilter=${tenant.defaultDomainName}&ID=!id&GroupType=!calculatedGroupType`, modalMessage: 'Are you sure you want to allow messages from people inside and outside the organisation? Remember this will not work if the group is AD Synched.', }, @@ -193,7 +193,7 @@ const Groups = () => { label: 'Delete Group', color: 'warning', modal: true, - modalUrl: `/api/ExecGroupsDelete?TenantFilter=${tenant.defaultDomainName}&ID=!mail&GroupType=!calculatedGroupType&DisplayName=!displayName`, + modalUrl: `/api/ExecGroupsDelete?TenantFilter=${tenant.defaultDomainName}&ID=!id&GroupType=!calculatedGroupType&DisplayName=!displayName`, modalMessage: 'Are you sure you want to delete this group.', }, ], diff --git a/src/views/identity/administration/Users.js b/src/views/identity/administration/Users.js index 4f64168fa419..bd3df44d1aa4 100644 --- a/src/views/identity/administration/Users.js +++ b/src/views/identity/administration/Users.js @@ -77,7 +77,7 @@ const Offcanvas = (row, rowIndex, formatExtraData) => { }, { label: 'Research Compromised Account', - link: `/identity/administration/ViewBec?userId=${row.id}&tenantDomain=${tenant.defaultDomainName}`, + link: `/identity/administration/ViewBec?userId=${row.id}&tenantDomain=${tenant.defaultDomainName}&ID=${row.userPrincipalName}`, color: 'info', }, { @@ -356,13 +356,11 @@ const Users = (row) => { { filterName: 'Users without a license', filter: '"assignedLicenses":[]' }, { filterName: 'Users with a license (Graph)', - filter: 'assignedLicenses/$count ne 0', - graphFilter: true, + filter: 'Graph: assignedLicenses/$count ne 0', }, { filterName: 'Users with a license & Enabled (Graph)', - filter: 'assignedLicenses/$count ne 0 and accountEnabled eq true', - graphFilter: true, + filter: 'Graph: assignedLicenses/$count ne 0 and accountEnabled eq true', }, ], columns, diff --git a/src/views/identity/administration/ViewBEC.js b/src/views/identity/administration/ViewBEC.js index d97e5639ae5a..bfe147e850cb 100644 --- a/src/views/identity/administration/ViewBEC.js +++ b/src/views/identity/administration/ViewBEC.js @@ -1,5 +1,5 @@ import React, { useEffect } from 'react' -import { CButton, CCallout, CLink } from '@coreui/react' +import { CButton, CCallout, CLink, CCardTitle } from '@coreui/react' import { CCardBody, CSpinner } from '@coreui/react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { @@ -27,6 +27,7 @@ import useConfirmModal from 'src/hooks/useConfirmModal' const ViewBec = () => { let query = useQuery() const userId = query.get('userId') + const userName = query.get('ID') const tenantDomain = query.get('tenantDomain') const [execBecRemediate, execRemediateResults] = useLazyGenericPostRequestQuery() const [execBecView, results] = useLazyExecBecCheckQuery() @@ -232,7 +233,7 @@ const ViewBec = () => { Business Email Compromise Overview - {userName}} button={ { value: 'UserLoggedIn', name: 'A user has logged in from any location', }, + { + value: 'Add service principal.', + name: 'Enterprise App Added', + }, + { + value: 'Remove service principal.', + name: 'Enterprise App Removed', + }, ]} />
    diff --git a/src/views/tenant/administration/ListAlertsQueue.js b/src/views/tenant/administration/ListAlertsQueue.js index d1da4bb53479..720e55a2fcc3 100644 --- a/src/views/tenant/administration/ListAlertsQueue.js +++ b/src/views/tenant/administration/ListAlertsQueue.js @@ -286,7 +286,7 @@ const ListAlertsQueue = () => { label: 'Delete webhook', color: 'info', modal: true, - modalUrl: `/api/RemoveWebhookAlert?CIPPID=!CIPPID&tenantfilter=!tenantName`, + modalUrl: `/api/RemoveWebhookAlert?CIPPID=!RowKey&tenantfilter=!PartitionKey`, modalMessage: 'Are you sure you want to delete this webhook alert?', }, ], diff --git a/src/views/tenant/administration/ListEnterpriseApps.js b/src/views/tenant/administration/ListEnterpriseApps.js index 8e1a3bfa9b01..d6ae08d59059 100644 --- a/src/views/tenant/administration/ListEnterpriseApps.js +++ b/src/views/tenant/administration/ListEnterpriseApps.js @@ -75,7 +75,6 @@ const EnterpriseApplications = () => { selector: (row) => row.homepage, sortable: true, exportSelector: 'homepage', - cell: cellDateFormatter({ format: 'short' }), }, ] return ( @@ -88,13 +87,12 @@ const EnterpriseApplications = () => { filterlist: [ { filterName: 'All Enterprise Apps', - filter: "tags/any(t:t eq 'WindowsAzureActiveDirectoryIntegratedApp')", - graphFilter: true, + filter: "Graph: tags/any(t:t eq 'WindowsAzureActiveDirectoryIntegratedApp')", }, { filterName: 'Enterprise Apps (SAML)', - filter: "tags/any(t:t eq 'WindowsAzureActiveDirectoryGalleryApplicationPrimaryV1')", - graphFilter: true, + filter: + "Graph: tags/any(t:t eq 'WindowsAzureActiveDirectoryGalleryApplicationPrimaryV1')", }, ], tableProps: { diff --git a/src/views/tenant/administration/ListGDAPRelationships.js b/src/views/tenant/administration/ListGDAPRelationships.js index 8efe4f6795ee..a5703bd84b22 100644 --- a/src/views/tenant/administration/ListGDAPRelationships.js +++ b/src/views/tenant/administration/ListGDAPRelationships.js @@ -68,7 +68,7 @@ const Actions = (row, rowIndex, formatExtraData) => { }) const tenant = useSelector((state) => state.app.currentTenant) - row?.accessDetails.unifiedRoles.map((role) => { + row?.accessDetails?.unifiedRoles?.map((role) => { for (var x = 0; x < GDAPRoles.length; x++) { if (GDAPRoles[x].ObjectId == role.roleDefinitionId) { extendedInfo.push({ diff --git a/src/views/tenant/standards/DomainsAnalyser.js b/src/views/tenant/standards/DomainsAnalyser.js index 6b8bd505d756..a92501851bec 100644 --- a/src/views/tenant/standards/DomainsAnalyser.js +++ b/src/views/tenant/standards/DomainsAnalyser.js @@ -274,6 +274,12 @@ const DomainsAnalyser = () => { tenantSelector={true} showAllTenantSelector={true} datatable={{ + filterlist: [ + { + filterName: 'Exclude onmicrosoft domains', + filter: 'Complex: domain notlike onmicrosoft', + }, + ], path: `/api/DomainAnalyser_List`, params: { tenantFilter: currentTenant.defaultDomainName }, columns, diff --git a/version_latest.txt b/version_latest.txt index d87edbfc1069..ecedc98d1d5a 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -4.2.1 \ No newline at end of file +4.3.1 \ No newline at end of file