diff --git a/public/version_latest.txt b/public/version_latest.txt index 64b5ae3938a0..ae153944ee8b 100644 --- a/public/version_latest.txt +++ b/public/version_latest.txt @@ -1 +1 @@ -4.4.0 \ No newline at end of file +4.5.0 \ No newline at end of file diff --git a/src/_nav.js b/src/_nav.js index 683cd830fdaa..852e4334d8cb 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -246,6 +246,45 @@ const _nav = [ }, ], }, + { + component: CNavGroup, + name: 'GDAP Management', + section: 'Settings', + to: '/cipp/gdap', + icon: , + items: [ + { + component: CNavItem, + name: 'Role Wizard', + to: '/tenant/administration/gdap-role-wizard', + }, + { + component: CNavItem, + name: 'GDAP Roles', + to: '/tenant/administration/gdap-roles', + }, + { + component: CNavItem, + name: 'Migration Wizard', + to: '/tenant/administration/gdap', + }, + { + component: CNavItem, + name: 'GDAP Migration Status', + to: '/tenant/administration/gdap-status', + }, + { + component: CNavItem, + name: 'Invite Wizard', + to: '/tenant/administration/gdap-invite', + }, + { + component: CNavItem, + name: 'GDAP Relationships', + to: '/tenant/administration/gdap-relationships', + }, + ], + }, { component: CNavGroup, name: 'Reports', @@ -293,15 +332,6 @@ const _nav = [ }, ], }, - // Coming in another branch (heads up) - //{ - //component: CNavGroup, - //name: 'Vulnerabilities', - //section: 'Security & Compliance', - //to: '/security/vulnerabilities', - //icon: , - //items: [], - //}, { component: CNavGroup, name: 'Defender', @@ -678,56 +708,15 @@ const _nav = [ name: 'Logbook', to: '/cipp/logs', }, - { component: CNavItem, name: 'SAM Setup Wizard', to: '/cipp/setup', }, - ], - }, - { - component: CNavGroup, - name: 'GDAP Migration', - section: 'Settings', - to: '/cipp/gdap', - icon: , - items: [ - { - component: CNavItem, - name: 'Role Wizard', - to: '/tenant/administration/gdap-role-wizard', - }, - { - component: CNavItem, - name: 'GDAP Roles', - to: '/tenant/administration/gdap-roles', - }, - { - component: CNavItem, - name: 'Migration Wizard', - to: '/tenant/administration/gdap', - }, - { - component: CNavItem, - name: 'GDAP Migration Status', - to: '/tenant/administration/gdap-status', - }, - { - component: CNavItem, - name: 'Invite Wizard', - to: '/tenant/administration/gdap-invite', - }, - { - component: CNavItem, - name: 'GDAP Relationships', - to: '/tenant/administration/gdap-relationships', - }, { component: CNavItem, - name: 'Documentation', - href: 'https://cipp.app/docs/user/usingcipp/GDAP/migration', - target: '_blank', + name: 'Log Out', + to: '/logout', }, ], }, diff --git a/src/components/buttons/TableModalButton.js b/src/components/buttons/TableModalButton.js new file mode 100644 index 000000000000..725405e7629e --- /dev/null +++ b/src/components/buttons/TableModalButton.js @@ -0,0 +1,40 @@ +import React from 'react' +import { CButton } from '@coreui/react' +import { ModalService } from '../utilities' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCheckCircle } from '@fortawesome/free-solid-svg-icons' +import { cellGenericFormatter } from '../tables/CellGenericFormat' + +export default function TableModalButton({ data, title, className }) { + const handleTable = (data) => { + const QueryColumns = [] + const columns = Object.keys(data[0]).map((key) => { + QueryColumns.push({ + name: key, + selector: (row) => row[key], // Accessing the property using the key + sortable: true, + exportSelector: key, + cell: cellGenericFormatter(), + }) + }) + ModalService.open({ + data: data, + componentType: 'table', + componentProps: { + columns: QueryColumns, + keyField: 'id', + }, + title: title, + size: 'lg', + }) + } + const buttonClass = 'btn ' + className + + return ( + handleTable(data)}> + <> + {title} ({data.length}) + + + ) +} diff --git a/src/components/buttons/index.js b/src/components/buttons/index.js index e0c639073418..c87617eb1456 100644 --- a/src/components/buttons/index.js +++ b/src/components/buttons/index.js @@ -1,5 +1,6 @@ import ExportCsvButton from 'src/components/buttons/CsvButton' import ExportPDFButton from 'src/components/buttons/PdfButton' import TitleButton from 'src/components/buttons/TitleButton' +import TableModalButton from 'src/components/buttons/TableModalButton' -export { ExportCsvButton, ExportPDFButton, TitleButton } +export { ExportCsvButton, ExportPDFButton, TitleButton, TableModalButton } diff --git a/src/components/tables/CellTable.js b/src/components/tables/CellTable.js index 004cdb715c48..c874a6854920 100644 --- a/src/components/tables/CellTable.js +++ b/src/components/tables/CellTable.js @@ -3,16 +3,22 @@ import { CButton } from '@coreui/react' import { ModalService } from '../utilities' import { CBadge } from '@coreui/react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faCheckCircle } from '@fortawesome/free-solid-svg-icons' +import { faCheckCircle, faTimesCircle } from '@fortawesome/free-solid-svg-icons' // 1. Import the required FontAwesome icon import { cellGenericFormatter } from './CellGenericFormat' -export default function cellTable(row, column, propertyName) { +export default function cellTable( + row, + column, + propertyName, + checkWhenZero = false, + crossWhenZero = false, +) { const handleTable = ({ row }) => { const QueryColumns = [] const columns = Object.keys(row[propertyName][0]).map((key) => { QueryColumns.push({ name: key, - selector: (row) => row[key], // Accessing the property using the key + selector: (row) => row[key], sortable: true, exportSelector: key, cell: cellGenericFormatter(), @@ -29,8 +35,24 @@ export default function cellTable(row, column, propertyName) { size: 'lg', }) } + //if the row propertyName is a bool, then return a check or cross + if (typeof row[propertyName] === 'boolean') { + if (row[propertyName]) { + return + } + return + } - if (!row[propertyName] || !Array.isArray(row[propertyName]) || row.length === 0) { + if (!row[propertyName] || !Array.isArray(row[propertyName]) || row[propertyName].length === 0) { + if (row[propertyName] === undefined) { + return + } + if (checkWhenZero) { + return + } + if (crossWhenZero) { + return + } return } @@ -41,6 +63,8 @@ export default function cellTable(row, column, propertyName) { ) } -export const cellTableFormatter = (propertyName) => (row, index, column, id) => { - return cellTable(row, column, propertyName) -} +export const cellTableFormatter = + (propertyName, checkWhenZero = false, crossWhenZero = false) => + (row, index, column, id) => { + return cellTable(row, column, propertyName, checkWhenZero, crossWhenZero) + } diff --git a/src/components/tables/CippTable.js b/src/components/tables/CippTable.js index 5fcb006c88be..75106ab21de4 100644 --- a/src/components/tables/CippTable.js +++ b/src/components/tables/CippTable.js @@ -79,27 +79,23 @@ FilterComponent.propTypes = { filterlist: PropTypes.arrayOf(PropTypes.object), onFilterPreset: PropTypes.func, } +const compareValues = (a, b) => { + if (a === null) return 1 + if (b === null) return -1 + if (typeof a === 'number' && typeof b === 'number') return a - b + if (typeof a === 'boolean' && typeof b === 'boolean') return a === b ? 0 : a ? -1 : 1 + return String(a).localeCompare(String(b), 'en', { numeric: true }) +} const customSort = (rows, selector, direction) => { return rows.sort((a, b) => { - // use the selector to resolve your field names by passing the sort comparitors - let aField - let bField - - aField = selector(a) - bField = selector(b) - - let comparison = 0 - - if (aField?.toString().localeCompare(bField, 'en', { numeric: true }) > 0) { - comparison = 1 - } else if (aField?.toString().localeCompare(bField, 'en', { numeric: true }) < 0) { - comparison = -1 - } - + let aField = selector(a) + let bField = selector(b) + let comparison = compareValues(aField, bField) return direction === 'desc' ? comparison * -1 : comparison }) } + export default function CippTable({ data, isFetching = false, @@ -115,6 +111,7 @@ export default function CippTable({ isModal = false, exportFiltered = false, filterlist, + showFilter = true, tableProps: { keyField = 'id', theme = 'cyberdrain', @@ -472,6 +469,10 @@ export default function CippTable({ ]) } + actions.forEach((action) => { + defaultActions.push(action) + }) + if (!disablePDFExport || !disableCSVExport) { const keys = [] const exportFormatter = {} @@ -582,9 +583,7 @@ export default function CippTable({ , ]) } - actions.forEach((action) => { - defaultActions.push(action) - }) + defaultActions.push([
- setFilterText(e.target.value)} - onFilterPreset={(e) => { - setFilterText(e) - }} - onClear={handleClear} - filterText={filterText} - filterlist={filterlist} - /> + {showFilter && ( + setFilterText(e.target.value)} + onFilterPreset={(e) => { + setFilterText(e) + }} + onClear={handleClear} + filterText={filterText} + filterlist={filterlist} + /> + )} {defaultActions}
diff --git a/src/components/utilities/UniversalSearch.js b/src/components/utilities/UniversalSearch.js index 0dadbe464659..0893132265d9 100644 --- a/src/components/utilities/UniversalSearch.js +++ b/src/components/utilities/UniversalSearch.js @@ -17,7 +17,7 @@ export const UniversalSearch = React.forwardRef( const handleKeyDown = (event) => { if (event.key === 'Enter') { - // on enter key, start the search + // on enter key, start the searchs getSearchItems({ path: `/api/ExecUniversalSearch?name=${searchValue}` }) } } @@ -80,7 +80,9 @@ const ResultsRow = ({ match }) => { const handleClick = () => { dispatch(hideSwitcher()) - navigate(`/identity/administration/users?customerId=${match._tenantId}`) + navigate( + `/identity/administration/users?customerId=${match._tenantId}&tableFilter=${match.userPrincipalName}`, + ) } return ( diff --git a/src/data/standards.json b/src/data/standards.json index 255449b17c3a..328a668fdc85 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -267,6 +267,13 @@ }, "label": "Set Outbound Spam Alert e-mail" }, + { + "name": "standards.SafeSendersDisable", + "cat": "Exchange", + "helpText": "", + "addedComponent": null, + "label": "Remove Safe Senders to prevent SPF bypass" + }, { "name": "standards.DisableSharedMailbox", "cat": "Exchange", @@ -332,7 +339,7 @@ }, { "name": "standards.ActivityBasedTimeout", - "cat": "SharePoint", + "cat": "Global", "helpText": "", "addedComponent": null, "label": "Enable 1 hour Activity based Timeout" @@ -415,6 +422,12 @@ }, "label": "Set inactive device retirement days" }, + { + "name": "standards.intuneRequireMFA", + "cat": "Intune", + "helpText": "", + "label": "Require Multifactor Authentication to register or join devices with Microsoft Entra" + }, { "name": "standards.sharingCapability.Enabled", "cat": "SharePoint", diff --git a/src/views/cipp/CIPPSettings.js b/src/views/cipp/CIPPSettings.js index 1716df3abee3..8c46d827521f 100644 --- a/src/views/cipp/CIPPSettings.js +++ b/src/views/cipp/CIPPSettings.js @@ -21,6 +21,7 @@ import { CSpinner, CCardText, CTooltip, + CFormSwitch, } from '@coreui/react' import { useGenericGetRequestQuery, @@ -82,6 +83,10 @@ import Skeleton from 'react-loading-skeleton' import { Buffer } from 'buffer' import Extensions from 'src/data/Extensions.json' import { CellDelegatedPrivilege } from 'src/components/tables/CellDelegatedPrivilege' +import { TableModalButton } from 'src/components/buttons' +import { cellTableFormatter } from 'src/components/tables/CellTable' +import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat' +import { check } from 'prettier' const CIPPSettings = () => { const [active, setActive] = useState(1) @@ -145,27 +150,8 @@ const CIPPSettings = () => { export default CIPPSettings -const checkAccessColumns = [ - { - name: 'Tenant Domain', - selector: (row) => row['TenantName'], - grow: 0, - }, - { - name: 'Result', - selector: (row) => row['Status'], - minWidth: '380px', - maxWidth: '380px', - }, - { - name: 'Missing GDAP Roles', - selector: (row) => row['GDAP'], - }, -] - const GeneralSettings = () => { const { data: versions, isSuccess: isSuccessVersion } = useLoadVersionsQuery() - const { data: tenants = [] } = useListTenantsQuery({ AllTenantSelector: false }) const [checkPermissions, permissionsResult] = useLazyExecPermissionsAccessCheckQuery() const [clearCache, clearCacheResult] = useLazyExecClearCacheQuery() @@ -173,8 +159,7 @@ const GeneralSettings = () => { const [selectedTenants, setSelectedTenants] = useState([]) const [showMaxSelected, setShowMaxSelected] = useState(false) const [tokenOffcanvasVisible, setTokenOffcanvasVisible] = useState(false) - const [runBackup, RunBackupResult] = useLazyGenericGetRequestQuery() - const [restoreBackup, restoreBackupResult] = useLazyGenericPostRequestQuery() + const [showExtendedInfo, setShowExtendedInfo] = useState(true) const maxSelected = 2 const tenantSelectorRef = useRef(null) @@ -189,6 +174,39 @@ const GeneralSettings = () => { } } + const checkAccessColumns = [ + { + name: 'Tenant Domain', + selector: (row) => row['TenantName'], + grow: 0, + cell: cellGenericFormatter(), + }, + { + name: 'Result', + selector: (row) => row['Status'], + minWidth: '380px', + maxWidth: '380px', + cell: cellGenericFormatter(), + }, + { + name: 'Missing GDAP Roles', + selector: (row) => row?.MissingRoles, + cell: cellTableFormatter('MissingRoles', true, false), + }, + { + name: 'Roles available', + selector: (row) => row?.GDAPRoles, + cell: cellTableFormatter('GDAPRoles', false, true), + omit: showExtendedInfo, + }, + { + name: 'SAM User Roles', + selector: (row) => row?.SAMUserRoles, + cell: cellTableFormatter('SAMUserRoles', false, true), + omit: showExtendedInfo, + }, + ] + const handleCheckAccess = () => { const mapped = tenants.reduce( (current, { customerId, ...rest }) => ({ @@ -286,7 +304,16 @@ const GeneralSettings = () => { const tableProps = { pagination: false, - subheader: false, + actions: [ + { + console.log(e) + setShowExtendedInfo(!e.target.checked) + }} + />, + ], } const downloadTxtFile = (data) => { const txtdata = [JSON.stringify(RunBackupResult.data.backup)] @@ -387,10 +414,29 @@ const GeneralSettings = () => { )} + {permissionsResult.data.Results?.MissingGroups.length > 0 && ( + <> + Your SAM User is missing the following group memberships. + + {permissionsResult.data.Results?.MissingGroups?.map((r, index) => ( + {r} + ))} + + + )} + {permissionsResult.data.Results?.CIPPGroupCount == 0 && ( + <> + NOTE: Your M365 GDAP groups were not set up by CIPP. Please check the groups + below to see if you have the correct GDAP permissions, or execute an access + check. + + )} {permissionsResult.data.Results?.AccessTokenDetails?.Name !== '' && ( <> - setTokenOffcanvasVisible(true)}>Details + setTokenOffcanvasVisible(true)}> + Details + { /> )} + {permissionsResult.data.Results?.Memberships !== '' && ( + <> + p['@odata.type'] == '#microsoft.graph.group', + )} + title="Groups" + /> + p['@odata.type'] == '#microsoft.graph.directoryRole', + )} + title="Roles" + /> + + )} )} @@ -445,7 +508,7 @@ const GeneralSettings = () => { - + Tenant Access Check @@ -493,6 +556,9 @@ const GeneralSettings = () => { {accessCheckResult.isSuccess && ( { - - - - - Run Backup - - - Click the button below to start a backup of all Settings - runBackup({ path: '/api/ExecRunBackup' })} - disabled={RunBackupResult.isFetching} - className="me-3 mt-3" - > - {RunBackupResult.isFetching && ( - - )} - Run backup - - handleChange(e)} - /> - inputRef.current.click()} - disabled={restoreBackupResult.isFetching} - className="me-3 mt-3" - > - {restoreBackupResult.isFetching && ( - - )} - Restore backup - - {restoreBackupResult.isSuccess && ( - <> - {restoreBackupResult.data.Results} - - )} - {RunBackupResult.isSuccess && ( - <> - - downloadTxtFile(RunBackupResult.data.backup)} - className="m-1" - > - Download Backup - - - - )} - - - - - - - ) } @@ -1319,46 +1323,40 @@ const PasswordSettings = () => { <> {getPasswordConfigResult.isUninitialized && getPasswordConfig({ path: '/api/ExecPasswordConfig?list=true' })} - - - Password Generation - - - Select a password style for generated passwords. - - {resolvers.map((r, index) => ( - switchResolver(r)} - color={ - r === getPasswordConfigResult.data?.Results?.passwordType - ? 'primary' - : 'secondary' - } - key={index} - > - {r} - - ))} - - {(editPasswordConfigResult.isSuccess || editPasswordConfigResult.isError) && ( - - {editPasswordConfigResult.isSuccess - ? editPasswordConfigResult.data.Results - : 'Error setting password style'} - - )} - - +

Password Style

+ + {resolvers.map((r, index) => ( + switchResolver(r)} + color={ + r === getPasswordConfigResult.data?.Results?.passwordType ? 'primary' : 'secondary' + } + key={index} + > + {r} + + ))} + + {(editPasswordConfigResult.isSuccess || editPasswordConfigResult.isError) && ( + + {editPasswordConfigResult.isSuccess + ? editPasswordConfigResult.data.Results + : 'Error setting password style'} + + )} ) } const DNSSettings = () => { + const [runBackup, RunBackupResult] = useLazyGenericGetRequestQuery() + const [restoreBackup, restoreBackupResult] = useLazyGenericPostRequestQuery() const [getDnsConfig, getDnsConfigResult] = useLazyGetDnsConfigQuery() const [editDnsConfig, editDnsConfigResult] = useLazyEditDnsConfigQuery() + const inputRef = useRef(null) const [alertVisible, setAlertVisible] = useState(false) @@ -1379,10 +1377,10 @@ const DNSSettings = () => { {getDnsConfigResult.isSuccess && ( - DNS Resolver + Application Settings - Select a DNS resolver to use for Domain Analysis. +

DNS Resolver

{resolvers.map((r, index) => ( { : 'Error setting resolver'} )} + + + + +

Settings Backup

+ runBackup({ path: '/api/ExecRunBackup' })} + disabled={RunBackupResult.isFetching} + className="me-3 mt-3" + > + {RunBackupResult.isFetching && ( + + )} + Run backup + + handleChange(e)} + /> + inputRef.current.click()} + disabled={restoreBackupResult.isFetching} + className="me-3 mt-3" + > + {restoreBackupResult.isFetching && ( + + )} + Restore backup + + {restoreBackupResult.isSuccess && ( + <> + {restoreBackupResult.data.Results} + + )} + {RunBackupResult.isSuccess && ( + <> + + downloadTxtFile(RunBackupResult.data.backup)} + className="m-1" + > + Download Backup + + + + )} +
)} diff --git a/src/views/cipp/Logs.js b/src/views/cipp/Logs.js index dc128d9a9721..0d339880d1ff 100644 --- a/src/views/cipp/Logs.js +++ b/src/views/cipp/Logs.js @@ -107,7 +107,7 @@ const Logs = () => { const DateFilter = query.get('DateFilter') //const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() const [visibleA, setVisibleA] = useState(false) - const [startDate, setStartDate] = useState(new Date()) + const [startDate, setStartDate] = useState(DateFilter ? new Date(DateFilter) : new Date()) const handleSubmit = async (values) => { Object.keys(values).filter(function (x) { if (values[x] === null) { @@ -117,7 +117,7 @@ const Logs = () => { }) const shippedValues = { SearchNow: true, - DateFilter: startDate.toLocaleDateString('en-GB').split('/').reverse().join(''), + DateFilter: startDate.toISOString().split('T')[0].replace(/-/g, ''), ...values, } var queryString = Object.keys(shippedValues) @@ -154,7 +154,7 @@ const Logs = () => { initialValues={{ Severity: severity, user: user, - DateFilter: DateFilter, + DateFilter: startDate.toISOString().split('T')[0].replace(/-/g, ''), }} onSubmit={handleSubmit} render={({ handleSubmit, submitting, values }) => { diff --git a/src/views/cipp/Setup.js b/src/views/cipp/Setup.js index 603e2a07a0e2..82da760206e7 100644 --- a/src/views/cipp/Setup.js +++ b/src/views/cipp/Setup.js @@ -48,7 +48,10 @@ Error.propTypes = { const Setup = () => { const [setupDone, setSetupdone] = useState(false) - const valbutton = (value) => (setupDone ? undefined : 'You must finish the wizard.') + const valbutton = (value) => + getResults.data?.step < 5 + ? undefined + : `You must finish the setup process. you are currently at step ${getResults.data?.step} of 5.` const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() const [genericGetRequest, getResults] = useLazyGenericGetRequestQuery() const onSubmit = (values) => { diff --git a/src/views/email-exchange/administration/EditMailboxPermissions.js b/src/views/email-exchange/administration/EditMailboxPermissions.js index b0649954994b..fd41f5af70e1 100644 --- a/src/views/email-exchange/administration/EditMailboxPermissions.js +++ b/src/views/email-exchange/administration/EditMailboxPermissions.js @@ -21,7 +21,11 @@ import { Form, Field } from 'react-final-form' import { RFFSelectSearch, RFFCFormSelect, RFFCFormCheck, RFFCFormInput } from 'src/components/forms' import { useListUsersQuery } from 'src/store/api/users' import { ModalService } from 'src/components/utilities' -import { useLazyGenericPostRequestQuery, useLazyGenericGetRequestQuery } from 'src/store/api/app' +import { + useLazyGenericPostRequestQuery, + useLazyGenericGetRequestQuery, + useGenericGetRequestQuery, +} from 'src/store/api/app' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' import { @@ -32,6 +36,9 @@ import { import { CippTable } from 'src/components/tables' import { useListMailboxDetailsQuery } from 'src/store/api/mailbox' import { CellBoolean } from 'src/components/tables' +import DatePicker from 'react-datepicker' +import 'react-datepicker/dist/react-datepicker.css' +import { RFFCFormSwitch } from 'src/components/forms' const formatter = (cell, warning = false, reverse = false, colourless = false) => CellBoolean({ cell, warning, reverse, colourless }) @@ -114,6 +121,9 @@ const MailboxSettings = () => { setActive(3)} href="#"> Mailbox Forwarding + setActive(4)} href="#"> + Out Of Office + @@ -127,6 +137,9 @@ const MailboxSettings = () => { + + + @@ -158,6 +171,11 @@ const MailboxSettings = () => { )} + {active === 4 && ( + <> + + + )} @@ -761,3 +779,227 @@ const ForwardingSettings = () => { ) } + +const OutOfOffice = () => { + const dispatch = useDispatch() + let query = useQuery() + const userId = query.get('userId') + const tenantDomain = query.get('tenantDomain') + const [queryError, setQueryError] = useState(false) + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + const [startDate, setStartDate] = useState(new Date()) + const [endDate, setEndDate] = useState(new Date()) + const { + data: user = {}, + isFetching: userIsFetching, + error: userError, + } = useListMailboxPermissionsQuery({ tenantDomain, userId }) + + const { + data: users = [], + isFetching: usersIsFetching, + error: usersError, + } = useListUsersQuery({ tenantDomain }) + + useEffect(() => { + if (postResults.isSuccess) { + } + if (!userId || !tenantDomain) { + ModalService.open({ + body: 'Error invalid request, could not load requested user.', + title: 'Invalid Request', + }) + setQueryError(true) + } else { + setQueryError(false) + } + }, [userId, tenantDomain, dispatch, postResults]) + const onSubmit = (values) => { + const shippedValues = { + user: userId, + tenantFilter: tenantDomain, + AutoReplyState: values.AutoReplyState ? 'Scheduled' : 'Disabled', + StartTime: startDate.toLocaleString(), + EndTime: endDate.toLocaleString(), + InternalMessage: values.InternalMessage ? values.InternalMessage : '', + ExternalMessage: values.ExternalMessage ? values.ExternalMessage : '', + } + //window.alert(JSON.stringify(shippedValues)) + genericPostRequest({ path: '/api/ExecSetOoO', values: shippedValues }) + } + const initialState = { + ...user, + } + + const formDisabled = queryError === true + + return ( + <> + {!queryError && ( + <> + {queryError && ( + + + + {/* @todo add more descriptive help message here */} + Failed to load user + + + + )} + + + {usersIsFetching && } + {userError && Error loading user} + {!usersIsFetching && ( +
{ + return ( + + + + + + + + + + setStartDate(date)} + showTimeSelect + /> + + + + + + setEndDate(date)} + showTimeSelect + /> + + + + + + + + + + + + + + + + Edit Out of Office + {postResults.isFetching && ( + + )} + + + + {postResults.isSuccess && ( + + {postResults.data.Results.map((result, idx) => ( +
  • {result}
  • + ))} +
    + )} +
    + ) + }} + /> + )} + + + + )} + + ) +} + +const OutOfOfficeSettings = () => { + const query = useQuery() + const userId = query.get('userId') + const tenantDomain = query.get('tenantDomain') + const tenantFilter = tenantDomain + const { + data: details, + isFetching, + error, + } = useGenericGetRequestQuery({ + path: '/api/ListOoO', + params: { userId, tenantFilter }, + }) + const combinedRegex = /(<([^>]+)>)|| /gi + const content = [ + { + heading: 'Auto Reply State', + body: formatter(details?.AutoReplyState, false, false, true), + }, + { + heading: 'Start Date/Time', + body: details?.StartTime ? details?.StartTime : 'N/A', + }, + { + heading: 'End Date/Time', + body: details?.EndTime ? details?.EndTime : 'N/A', + }, + { + heading: 'Internal Message', + body: details?.InternalMessage ? details?.InternalMessage.replace(combinedRegex, '') : 'N/A', + }, + { + heading: 'External Message', + body: details?.ExternalMessage ? details?.ExternalMessage.replace(combinedRegex, '') : 'N/A', + }, + ] + return ( + + {isFetching && ( + + Loading + + )} + {!isFetching && ( + + {content.map((item, index) => ( +
    +
    {item.heading}
    +

    {item.body}

    +
    + ))} +
    + )} + {error && Could not connect to API: {error.message}} +
    + ) +} diff --git a/src/views/email-exchange/transport/TransportRules.js b/src/views/email-exchange/transport/TransportRules.js index ecb39e6d9af6..77f26875c406 100644 --- a/src/views/email-exchange/transport/TransportRules.js +++ b/src/views/email-exchange/transport/TransportRules.js @@ -122,7 +122,7 @@ const TransportRulesList = () => { tenantSelector={true} titleButton={ <> - + } datatable={{ diff --git a/src/views/endpoint/applications/ApplicationsAddChocoApp.js b/src/views/endpoint/applications/ApplicationsAddChocoApp.js index 4b46dcec0efa..6f518d6f5d07 100644 --- a/src/views/endpoint/applications/ApplicationsAddChocoApp.js +++ b/src/views/endpoint/applications/ApplicationsAddChocoApp.js @@ -18,6 +18,7 @@ import { CippWizard } from 'src/components/layout' import { WizardTableField } from 'src/components/tables' import PropTypes from 'prop-types' import { + Condition, RFFCFormCheck, RFFCFormInput, RFFCFormRadio, @@ -57,6 +58,9 @@ const ApplyStandard = () => { values.selectedTenants.map( (tenant) => (values[`Select_${tenant.defaultDomainName}`] = tenant.defaultDomainName), ) + if (values.AssignTo === 'customGroup') { + values.AssignTo = values.customGroup + } genericPostRequest({ path: '/api/AddChocoApp', values: values }) } const handleSearch = async ({ searchString, customRepo }) => { @@ -87,7 +91,6 @@ const ApplyStandard = () => { console.log(value) return obj.packagename === value }) - console.log(template[0]) onChange(template[0][set]) }} @@ -246,6 +249,18 @@ const ApplyStandard = () => { name="AssignTo" label="Assign to all users and devices" > + + + +
    diff --git a/src/views/endpoint/applications/ApplicationsAddRMM.js b/src/views/endpoint/applications/ApplicationsAddRMM.js index 5d994c7dbbca..da43bbca5a29 100644 --- a/src/views/endpoint/applications/ApplicationsAddRMM.js +++ b/src/views/endpoint/applications/ApplicationsAddRMM.js @@ -37,6 +37,9 @@ const AddRMM = () => { values.selectedTenants.map( (tenant) => (values[`Select_${tenant.defaultDomainName}`] = tenant.defaultDomainName), ) + if (values.AssignTo === 'customGroup') { + values.AssignTo = values.customGroup + } genericPostRequest({ path: '/api/AddMSPApp', values: values }) } @@ -312,6 +315,18 @@ const AddRMM = () => { name="AssignTo" label="Assign to all users and devices" > + + + +
    diff --git a/src/views/endpoint/applications/ApplicationsAddWinGet.js b/src/views/endpoint/applications/ApplicationsAddWinGet.js index 14424f1390a9..72a1dc7937f4 100644 --- a/src/views/endpoint/applications/ApplicationsAddWinGet.js +++ b/src/views/endpoint/applications/ApplicationsAddWinGet.js @@ -18,6 +18,7 @@ import { CippWizard } from 'src/components/layout' import { WizardTableField } from 'src/components/tables' import PropTypes from 'prop-types' import { + Condition, RFFCFormCheck, RFFCFormInput, RFFCFormRadio, @@ -54,6 +55,9 @@ const AddWinGet = () => { const [searchPostRequest, foundPackages] = useLazyGenericPostRequestQuery() const handleSubmit = async (values) => { + if (values.AssignTo === 'customGroup') { + values.AssignTo = values.customGroup + } values.selectedTenants.map( (tenant) => (values[`Select_${tenant.defaultDomainName}`] = tenant.defaultDomainName), ) @@ -240,6 +244,18 @@ const AddWinGet = () => { name="AssignTo" label="Assign to all users and devices" > + + + + diff --git a/src/views/endpoint/intune/MEMAddPolicy.js b/src/views/endpoint/intune/MEMAddPolicy.js index 327954c91f40..f87f6b87dd2a 100644 --- a/src/views/endpoint/intune/MEMAddPolicy.js +++ b/src/views/endpoint/intune/MEMAddPolicy.js @@ -45,6 +45,9 @@ const AddPolicy = () => { (tenant) => (values[`Select_${tenant.defaultDomainName}`] = tenant.defaultDomainName), ) values.TemplateType = values.Type + if (values.AssignTo === 'customGroup') { + values.AssignTo = values.customGroup + } genericPostRequest({ path: '/api/AddPolicy', values: values }) } const [matchMap, setMatchMap] = useState([]) @@ -234,6 +237,18 @@ const AddPolicy = () => { name="AssignTo" label="Assign to all users and devices" > + + + +
    diff --git a/src/views/endpoint/intune/MEMListPolicyTemplates.js b/src/views/endpoint/intune/MEMListPolicyTemplates.js index 645744383d56..851c6731bedd 100644 --- a/src/views/endpoint/intune/MEMListPolicyTemplates.js +++ b/src/views/endpoint/intune/MEMListPolicyTemplates.js @@ -69,7 +69,7 @@ const AutopilotListTemplates = () => { selector: (row) => row['displayName'], sortable: true, cell: (row) => CellTip(row['displayName']), - exportSelector: 'Displayname', + exportSelector: 'displayName', minWidth: '400px', maxWidth: '400px', }, @@ -78,7 +78,7 @@ const AutopilotListTemplates = () => { selector: (row) => row['description'], sortable: true, cell: (row) => CellTip(row['description']), - exportSelector: 'Description', + exportSelector: 'description', minWidth: '400px', maxWidth: '400px', }, diff --git a/src/views/home/Home.js b/src/views/home/Home.js index c802805552af..761be7b0d35d 100644 --- a/src/views/home/Home.js +++ b/src/views/home/Home.js @@ -187,12 +187,8 @@ const Home = () => { - diff --git a/src/views/identity/administration/OffboardingWizard.js b/src/views/identity/administration/OffboardingWizard.js index a5bf2288504a..71e886944b03 100644 --- a/src/views/identity/administration/OffboardingWizard.js +++ b/src/views/identity/administration/OffboardingWizard.js @@ -60,6 +60,7 @@ const OffboardingWizard = () => { removeRules: values.RemoveRules, removeMobile: values.RemoveMobile, keepCopy: values.keepCopy, + removePermissions: values.removePermissions, } //alert(JSON.stringify(values, null, 2)) @@ -125,6 +126,7 @@ const OffboardingWizard = () => { + @@ -260,6 +262,14 @@ const OffboardingWizard = () => { icon={props.values.RemoveRules ? faCheck : faTimes} /> + + Remove all mailbox permissions + + Remove Licenses { user: row.userPrincipalName, TenantFilter: tenant.defaultDomainName, message: row.message, + AutoReplyState: 'Enabled', }, modalUrl: `/api/ExecSetOoO`, modalInput: true, @@ -163,7 +164,7 @@ const Offcanvas = (row, rowIndex, formatExtraData) => { modalBody: { user: row.userPrincipalName, TenantFilter: tenant.defaultDomainName, - Disable: true, + AutoReplyState: 'Disabled', }, modalUrl: `/api/ExecSetOoO`, modalMessage: 'Are you sure you want to disable the out of office?', diff --git a/src/views/identity/reports/MFAReport.js b/src/views/identity/reports/MFAReport.js index da29c7e33b87..5dbae89bb4ad 100644 --- a/src/views/identity/reports/MFAReport.js +++ b/src/views/identity/reports/MFAReport.js @@ -134,7 +134,7 @@ const MFAList = () => { datatable={{ filterlist: [ { filterName: 'Enabled users', filter: '"accountEnabled":true' }, - { filterName: 'Licensed users', filter: '"isLicensed":"true"' }, + { filterName: 'Licensed users', filter: 'Complex: IsLicensed eq true' }, ], columns: tenant.defaultDomainName === 'AllTenants' ? Altcolumns : columns, path: '/api/ListMFAUsers', diff --git a/src/views/tenant/administration/ListGDAPRelationships.js b/src/views/tenant/administration/ListGDAPRelationships.js index a5703bd84b22..dcba0648a907 100644 --- a/src/views/tenant/administration/ListGDAPRelationships.js +++ b/src/views/tenant/administration/ListGDAPRelationships.js @@ -34,7 +34,7 @@ const RefreshAction = () => { {isLoading && } {error && } {isSuccess && } - Map GDAP Groups + Map recently approved relationships ) } @@ -89,6 +89,13 @@ const Actions = (row, rowIndex, formatExtraData) => { title={'GDAP - ' + row?.customer?.displayName} extendedInfo={extendedInfo} actions={[ + { + label: 'Enable automatic extension', + color: 'info', + modal: true, + modalUrl: `/api/ExecAutoExtendGDAP?ID=${row.id}`, + modalMessage: 'Are you sure you want to enable auto-extend for this relationship', + }, { label: 'Terminate Relationship', color: 'danger', @@ -149,6 +156,13 @@ const GDAPRelationships = () => { exportSelector: 'endDateTime', cell: cellDateFormatter({ format: 'short' }), }, + { + name: 'Auto Extend', + selector: (row) => row['autoExtendDuration'], + sortable: true, + exportSelector: 'endDateTime', + cell: (row) => (row['autoExtendDuration'] === 'PT0S' ? 'No' : 'Yes'), + }, { name: 'Actions', cell: Actions, @@ -158,6 +172,7 @@ const GDAPRelationships = () => { return (
    } capabilities={{ allTenants: true, helpContext: 'https://google.com' }} title="GDAP Relationship List" tenantSelector={false} @@ -176,8 +191,13 @@ const GDAPRelationships = () => { modalUrl: `/api/ExecDeleteGDAPRelationship?&GDAPID=!id`, modalMessage: 'Are you sure you want to terminate these relationships?', }, + { + label: 'Auto Extend Relationship', + modal: true, + modalUrl: `/api/ExecAutoExtendGDAP?ID=!id`, + modalMessage: 'Are you sure you want to enable automatic extension?', + }, ], - actions: [], }, keyField: 'id', columns, diff --git a/src/views/tenant/standards/ListAppliedStandards.js b/src/views/tenant/standards/ListAppliedStandards.js index 7f1382a5e149..d22f03d05862 100644 --- a/src/views/tenant/standards/ListAppliedStandards.js +++ b/src/views/tenant/standards/ListAppliedStandards.js @@ -21,6 +21,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import Skeleton from 'react-loading-skeleton' import { CippTable } from 'src/components/tables' import allStandardsList from 'src/data/standards' +import { useState } from 'react' +import CippCodeOffCanvas from 'src/components/utilities/CippCodeOffcanvas' const RefreshAction = () => { const [execStandards, execStandardsResults] = useLazyGenericGetRequestQuery() @@ -80,6 +82,43 @@ const DeleteAction = () => { ) } const ListAppliedStandards = () => { + const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery() + + const Offcanvas = (row, rowIndex, formatExtraData) => { + const [ocVisible, setOCVisible] = useState(false) + const handleDeleteIntuneTemplate = (apiurl, message) => { + ModalService.confirm({ + title: 'Confirm', + body:
    {message}
    , + onConfirm: () => ExecuteGetRequest({ path: apiurl }), + confirmLabel: 'Continue', + cancelLabel: 'Cancel', + }) + } + return ( + <> + + handleDeleteIntuneTemplate( + `api/RemoveStandard?ID=${row.displayName}`, + 'Do you want to delete this standard?', + ) + } + > + + + setOCVisible(false)} + /> + + ) + } const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() @@ -124,6 +163,11 @@ const ListAppliedStandards = () => { sortable: true, exportSelector: 'StandardsExport', }, + { + name: 'Actions', + cell: Offcanvas, + maxWidth: '80px', + }, ] const [intuneGetRequest, intuneTemplates] = useLazyGenericGetRequestQuery() const [transportGetRequest, transportTemplates] = useLazyGenericGetRequestQuery() @@ -383,9 +427,6 @@ const ListAppliedStandards = () => { ))} - {postResults.isSuccess && ( - {postResults.data.Results} - )}
    Templates

    @@ -407,7 +448,7 @@ const ListAppliedStandards = () => { name: template.Displayname, }))} placeholder="Select a template" - label="Choose your intune templates to apply" + label="Choose your Intune templates to apply" /> )} @@ -451,7 +492,7 @@ const ListAppliedStandards = () => { name: template.displayName, }))} placeholder="Select a template" - label="Choose your intune templates to apply" + label="Choose your Conditional Access templates to apply" /> )} @@ -473,7 +514,7 @@ const ListAppliedStandards = () => { name: template.name, }))} placeholder="Select a template" - label="Choose your intune templates to apply" + label="Choose your Transport Rule templates to apply" /> )} @@ -494,7 +535,7 @@ const ListAppliedStandards = () => { name: template.Displayname, }))} placeholder="Select a template" - label="Choose your group templates to apply" + label="Choose your Group templates to apply" /> )} @@ -533,6 +574,10 @@ const ListAppliedStandards = () => { {listStandardsAllTenants && ( + {getResults.isLoading && } + {getResults.isSuccess && ( + {getResults.data?.Results} + )}