-
{label}
+
+ {label}
+ {refreshFunction && (
+
+
+
+
+
+ )}
+
{onChange && (
)}
{!onChange && (
@@ -417,7 +463,11 @@ export const RFFSelectSearch = ({
disabled={disabled}
options={selectSearchvalues}
placeholder={placeholder}
+ onInputChange={setOnInputChange}
isMulti={multi}
+ inputValue={inputText}
+ isLoading={isLoading}
+ {...props}
/>
)}
{meta.error && meta.touched &&
{meta.error} }
@@ -432,6 +482,9 @@ RFFSelectSearch.propTypes = {
...sharedPropTypes,
multi: PropTypes.bool,
placeholder: PropTypes.string,
+ onInputChange: PropTypes.func,
+ isLoading: PropTypes.bool,
+ refreshFunction: PropTypes.func,
values: PropTypes.arrayOf(PropTypes.shape({ value: PropTypes.string, name: PropTypes.string }))
.isRequired,
}
diff --git a/src/components/header/AppHeaderDropdown.js b/src/components/header/AppHeaderDropdown.jsx
similarity index 100%
rename from src/components/header/AppHeaderDropdown.js
rename to src/components/header/AppHeaderDropdown.jsx
diff --git a/src/components/header/AppHeaderSearch.js b/src/components/header/AppHeaderSearch.jsx
similarity index 100%
rename from src/components/header/AppHeaderSearch.js
rename to src/components/header/AppHeaderSearch.jsx
diff --git a/src/components/layout/AppBreadcrumb.js b/src/components/layout/AppBreadcrumb.jsx
similarity index 100%
rename from src/components/layout/AppBreadcrumb.js
rename to src/components/layout/AppBreadcrumb.jsx
diff --git a/src/components/layout/AppFooter.js b/src/components/layout/AppFooter.js
deleted file mode 100644
index 967f7bb3d1ff..000000000000
--- a/src/components/layout/AppFooter.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import React from 'react'
-import { CFooter, CImage, CLink } from '@coreui/react'
-import { Link } from 'react-router-dom'
-import huntressLogo from 'src/assets/images/huntress_teal.png'
-import dattoLogo from 'src/assets/images/datto.png'
-import rewstLogo from 'src/assets/images/rewst.png'
-import netfriends from 'src/assets/images/netfriends.png'
-//todo: Add darkmode detection and change logos accordingly.
-const AppFooter = () => {
- return (
-
-
-
- This application is sponsored by{' '}
-
-
- {' '}
-
-
- {' '}
-
-
- {' '}
-
-
-
-
-
-
- License
-
-
- )
-}
-
-export default React.memo(AppFooter)
diff --git a/src/components/layout/AppFooter.jsx b/src/components/layout/AppFooter.jsx
new file mode 100644
index 000000000000..2c27efe8efd3
--- /dev/null
+++ b/src/components/layout/AppFooter.jsx
@@ -0,0 +1,48 @@
+import React from 'react'
+import { CFooter, CImage, CLink } from '@coreui/react'
+import { Link } from 'react-router-dom'
+import { useSelector } from 'react-redux'
+import { useMediaPredicate } from 'react-media-hook'
+
+const AppFooter = () => {
+ const currentTheme = useSelector((state) => state.app.currentTheme)
+ const preferredTheme = useMediaPredicate('(prefers-color-scheme: dark)') ? 'impact' : 'cyberdrain'
+ const isDark =
+ currentTheme === 'impact' || (currentTheme === 'default' && preferredTheme === 'impact')
+
+ const netfriends = isDark ? '/img/netfriends_dark.png' : '/img/netfriends.png'
+ const datto = isDark ? '/img/datto.png' : '/img/datto.png'
+ const huntress = isDark ? '/img/huntress_teal.png' : '/img/huntress_teal.png'
+ const rewst = isDark ? '/img/rewst_dark.png' : '/img/rewst.png'
+ const ninjaone = isDark ? '/img/ninjaone_dark.png' : '/img/ninjaone.png'
+
+ return (
+
+
+
+ This application is sponsored by
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ License
+
+
+ )
+}
+
+export default React.memo(AppFooter)
diff --git a/src/components/layout/AppHeader.js b/src/components/layout/AppHeader.jsx
similarity index 70%
rename from src/components/layout/AppHeader.js
rename to src/components/layout/AppHeader.jsx
index 48dfc929c380..f31d73a124e0 100644
--- a/src/components/layout/AppHeader.js
+++ b/src/components/layout/AppHeader.jsx
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { useState, useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import {
CAlert,
@@ -16,17 +16,12 @@ import {
} from '@coreui/react'
import { AppHeaderSearch } from 'src/components/header'
import { TenantSelector } from '../utilities'
-import cyberdrainlogolight from 'src/assets/images/CIPP.png'
-import cyberdrainlogodark from 'src/assets/images/CIPP_Dark.png'
-
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
-import { faCaretSquareLeft, faCaretSquareRight } from '@fortawesome/free-solid-svg-icons'
+import { faBars } from '@fortawesome/free-solid-svg-icons'
import { setCurrentTheme, setUserSettings, toggleSidebarShow } from 'src/store/features/app'
import { useMediaPredicate } from 'react-media-hook'
import { useGenericGetRequestQuery, useLoadAlertsDashQuery } from 'src/store/api/app'
import { useLocation } from 'react-router-dom'
-import { useState } from 'react'
-import { useEffect } from 'react'
const AppHeader = () => {
const dispatch = useDispatch()
@@ -77,33 +72,20 @@ const AppHeader = () => {
return (
<>
-
-
-
- dispatch(toggleSidebarShow({ sidebarShow }))}
- >
-
-
-
-
+
+ dispatch(toggleSidebarShow({ sidebarShow }))}
+ style={{ marginInlineStart: '-50x' }}
+ >
+
+
+
@@ -141,19 +123,17 @@ const AppHeader = () => {
-
- {dashboard &&
- dashboard.length >= 1 &&
- dashboard.map((item, index) => (
-
-
- {item.Alert} Link
-
-
- ))}
+
+ {dashboard &&
+ dashboard.length >= 1 &&
+ dashboard.map((item, index) => (
+
+
+ {item.Alert} Link
+
+
+ ))}
+
>
)
}
diff --git a/src/components/layout/AppSidebar.js b/src/components/layout/AppSidebar.js
deleted file mode 100644
index 980832641a71..000000000000
--- a/src/components/layout/AppSidebar.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from 'react'
-import { useSelector, useDispatch } from 'react-redux'
-import { CSidebar, CSidebarNav } from '@coreui/react'
-import { AppSidebarNav } from 'src/components/layout'
-import SimpleBar from 'simplebar-react'
-import 'simplebar/dist/simplebar.min.css'
-import navigation from 'src/_nav'
-import { setSidebarVisible } from 'src/store/features/app'
-
-const AppSidebar = () => {
- const dispatch = useDispatch()
- const unfoldable = useSelector((state) => state.app.sidebarUnfoldable)
- const sidebarShow = useSelector((state) => state.app.sidebarShow)
-
- return (
-
{
- dispatch(setSidebarVisible({ visible }))
- }}
- >
-
-
-
-
-
-
- )
-}
-
-export default React.memo(AppSidebar)
diff --git a/src/components/layout/AppSidebar.jsx b/src/components/layout/AppSidebar.jsx
new file mode 100644
index 000000000000..f680f42116d1
--- /dev/null
+++ b/src/components/layout/AppSidebar.jsx
@@ -0,0 +1,51 @@
+import React from 'react'
+import { useSelector, useDispatch } from 'react-redux'
+import {
+ CCloseButton,
+ CHeaderNav,
+ CImage,
+ CSidebar,
+ CSidebarBrand,
+ CSidebarNav,
+} from '@coreui/react'
+import { AppSidebarNav } from 'src/components/layout'
+import SimpleBar from 'simplebar-react'
+import 'simplebar/dist/simplebar.min.css'
+import navigation from 'src/_nav'
+
+const AppSidebar = () => {
+ const i =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA8AAAAGACAYAAABiEQveAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAJdoSURBVHhe7d0FYBRXtwfw/2o8EIKF4A4FSoFSoA6lpe7u7u6v7tRb6u7u3lKnLW0pFHcnuMR19Z0zO/AFCm1IZjc7u//fe+fbmbtpSDYzd+6Ze+deEBERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERJTaH+UpERPSfwuGwubVNHSVyJUISfolhEjub27Xp+9kSx0u4teA/6Ne/K7FBwqkFtbgkZkr8KKHfS69txRKLJP6Vw8HLIBERUTLhlZ+IiDbzL0muJpeabOZJDJHwSfSVGCGhCa4mqf0kWprbastkNZpq/5uaKE+R0OucR+I3iQkSqRITJZZK6C+6ZWK+CZNjIiKixMOrOxFRkttGwpsi0UaivcS+EkGJAyQ0uc2QaCZhV9o7XGq+fiahCfMPEgsl1khUSvwDE2IiIiL749WciCgJbZH06rWgm0QnCe3R3UOilcRACU0Ok+VaoUn+dIkCiT8lfpf4W6JMQt/bhMkwERGRPfEKTkSU4LbSw6tDgvU5XR263EviIIm2ZhltTnuFqyS+lJgl8bWE9hL/A5NiIiKi+MerNRFRgtnGkOYeEp0lDpHQXt7+EjqUuTb9D3ld+Hc6bFqHS8+W+FxiscQ/EmImw0RERPGJV2giogSwlaTXK6FDmbtKHC2hMzFnSZC1lkn8LPGNxCSJORL/wISYiIgoPvCKTERkY1skvukS+tzuYRIHS+hzvRQ7FRKaBH8qob3DcyU2w0SYiIiocfFKTERkI1vp6U2T0CWJjpDY+EwvNT5dXukPiQ8lvpfQybU2YSJMRETUOHgFJiKyga0kvrtL6PO82tOrz/dS/KqWeFNCJ9LS54eLJDZhMkxERBQ7vOoSEcWxLRJffYb3NAlNfLW3l3W4/ayTeF7iAwldYmnTH5iJMBERUfTxaktEFGe2SHp1Hd6eEldI7CXR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9QPUunmJhH4/5ZbQnl2dEVyPZ52LQet4/R5bvbCw7iZKHjzbiciWtmPIbzsJzRK29R9oPagT62jjqU7YUIoPTIAtS4B1hEXZxuN6O86tbhLbejRBv5mOmqjz0mfxel4xAbYsAX5N4hTd0L/1dhxnKRJ6rG3rP9CkV1caMNbi/S+sv4lo450yIiJb0UbMf4VJJ8yZIaFDl7cW+p6R/G7te2wtiOJClA7FrR3ztaMWfWZya+eUhp5Xm5LfrX2fLYOSy9aOgdpRiz43/G91uPbybkp+t/a9agcRERNgIkpYW2v8/FsQ0X/b2rnzX0G0vbZ2HNUliIj+CxNgIiIiO6rzCFIiIiLaiAkwERERERERJQUmwERERHbE0Z5ERETbjQkwERGRHXEINBER0XZjAkxERERERERJgQkwERERERERJQUmwERERERERJQUmAATERHZESfBIiIi2m5MgImIiOyIk2ARERFtNybARERERERElBSYABMREREREVFSYAJMRERkR3wGmIiIaLsxASYiIiIiIqKkwASYiIiIiIiIkgITYCIiIjviLNBERETbjQkwERERERERJQUmwERERERERJQUmAATERERERFRUmACTEREREREREmBCTAREZEdcR1gIiKi7cYEmIiIyI44CzQREdF2YwJMRERERERESYEJMBERERERESUFJsBERERERESUFJgAExERERERUVJgAkxERGQy5pXyB4FgKH4jFJL/5wxYdheUv+M//rbxFr6gnBM81ogosXARBSIisqVweLOG+TkSz0Q260e/24tvfYhPx45DitcTKYxD+ns3bdoE9994GZpmZ5ml9bZaortEmcPBJsHWbHGc9ZP4W8Jl7NXTijXrcNWdj8BfXQ2nM377IsqqqnHyEQfghMP2N0sa5DWJU3SDxxoRNSbWQEREZEtWJ8DK7w9Ae+bi+eKov7XmD16Px4pEggnwf4hGAqzf0+f3y2t8N8T0N3e5nPC43ZGChmECTERxgTUQERHZUjQS4CTEBPg/RCMBTlJMgIkoLvAZYCIiIiIiIkoKTICJiIiIiIgoKTABJiIiIiIioqTABJiIiIiIiIiSAhNgIiIiIiIiSgpMgImIiIiIiCgpMAEmIiIiIiKipMAEmIiIiIiIiJICE2AiIiIiIiJKCkyAiYiIiIiIKCkwASYiIiIiIqKkwASYiIiIiIiIkgITYCIiIiIiIkoKTICJiIiIiIgoKTABJiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIqLtB/w/6jKqVLW7kBgAAAAASUVORK5CYII='
+
+ const dispatch = useDispatch()
+ const sidebarShow = useSelector((state) => state.app.sidebarShow)
+ if (!i.includes('JGySCBt1QXmNc')) {
+ throw ''
+ }
+ return (
+
{
+ dispatch({ type: 'set', sidebarShow: visible })
+ }}
+ position="fixed"
+ unfoldable={false}
+ visible={sidebarShow}
+ >
+
+
+
+
+ dispatch({ type: 'set', sidebarShow: false })}
+ />
+
+
+
+
+
+
+ )
+}
+
+export default React.memo(AppSidebar)
diff --git a/src/components/layout/AppSidebarNav.js b/src/components/layout/AppSidebarNav.jsx
similarity index 100%
rename from src/components/layout/AppSidebarNav.js
rename to src/components/layout/AppSidebarNav.jsx
diff --git a/src/components/layout/CippContentCard.js b/src/components/layout/CippContentCard.jsx
similarity index 91%
rename from src/components/layout/CippContentCard.js
rename to src/components/layout/CippContentCard.jsx
index efb91ee7d9d2..db535d3809be 100644
--- a/src/components/layout/CippContentCard.js
+++ b/src/components/layout/CippContentCard.jsx
@@ -29,4 +29,5 @@ CippContentCard.propTypes = {
button: PropTypes.element,
bodyClass: PropTypes.string,
className: PropTypes.string,
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
}
diff --git a/src/components/layout/CippMasonry.js b/src/components/layout/CippMasonry.jsx
similarity index 95%
rename from src/components/layout/CippMasonry.js
rename to src/components/layout/CippMasonry.jsx
index 7d1d35f7a6e7..53dcb7e50fd5 100644
--- a/src/components/layout/CippMasonry.js
+++ b/src/components/layout/CippMasonry.jsx
@@ -46,7 +46,7 @@ export function CippMasonryItem({ size, children, className = null }) {
CippMasonryItem.propTypes = {
size: PropTypes.oneOf(['single', 'double', 'triple', 'full', 'half']),
- children: PropTypes.object,
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
className: PropTypes.string,
}
diff --git a/src/components/layout/CippPage.js b/src/components/layout/CippPage.jsx
similarity index 100%
rename from src/components/layout/CippPage.js
rename to src/components/layout/CippPage.jsx
diff --git a/src/components/layout/CippWizard.js b/src/components/layout/CippWizard.jsx
similarity index 93%
rename from src/components/layout/CippWizard.js
rename to src/components/layout/CippWizard.jsx
index 8cfa96bf232f..9ebfc1097fae 100644
--- a/src/components/layout/CippWizard.js
+++ b/src/components/layout/CippWizard.jsx
@@ -8,11 +8,12 @@ export default class CippWizard extends React.Component {
static propTypes = {
wizardTitle: PropTypes.string,
onSubmit: PropTypes.func.isRequired,
- children: PropTypes.arrayOf(PropTypes.element),
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
initialValues: PropTypes.any,
onPageChange: PropTypes.func,
nextPage: PropTypes.func,
previousPage: PropTypes.func,
+ hideSubmit: PropTypes.bool,
}
static defaultProps = {
@@ -27,6 +28,7 @@ export default class CippWizard extends React.Component {
page: 0,
values: props.initialValues,
wizardTitle: props.wizardTitle,
+ hideSubmit: props.hideSubmit,
}
}
@@ -64,7 +66,7 @@ export default class CippWizard extends React.Component {
render() {
const { children } = this.props
- const { page, values, wizardTitle } = this.state
+ const { page, values, wizardTitle, hideSubmit } = this.state
const activePage = React.Children.toArray(children)[page]
const isLastPage = page === React.Children.count(children) - 1
@@ -104,7 +106,7 @@ export default class CippWizard extends React.Component {
Next »
)}
- {isLastPage && (
+ {isLastPage && !hideSubmit && (
<>
Submit
diff --git a/src/components/tables/CellBadge.js b/src/components/tables/CellBadge.jsx
similarity index 100%
rename from src/components/tables/CellBadge.js
rename to src/components/tables/CellBadge.jsx
diff --git a/src/components/tables/CellBoolean.js b/src/components/tables/CellBoolean.jsx
similarity index 97%
rename from src/components/tables/CellBoolean.js
rename to src/components/tables/CellBoolean.jsx
index 8d5a31138f63..fc23282f7a2c 100644
--- a/src/components/tables/CellBoolean.js
+++ b/src/components/tables/CellBoolean.jsx
@@ -63,7 +63,7 @@ export default function CellBoolean({
}
}
- if (cell === '' && !noDataIsFalse) {
+ if ((cell === '' && !noDataIsFalse) || cell === undefined) {
return
} else if (colourless && warning && reverse) {
return nocolour(colourless, normalized ? : )
diff --git a/src/components/tables/CellDate.js b/src/components/tables/CellDate.jsx
similarity index 100%
rename from src/components/tables/CellDate.js
rename to src/components/tables/CellDate.jsx
diff --git a/src/components/tables/CellDelegatedPrivilege.js b/src/components/tables/CellDelegatedPrivilege.jsx
similarity index 100%
rename from src/components/tables/CellDelegatedPrivilege.js
rename to src/components/tables/CellDelegatedPrivilege.jsx
diff --git a/src/components/tables/CellGenericFormat.js b/src/components/tables/CellGenericFormat.jsx
similarity index 56%
rename from src/components/tables/CellGenericFormat.js
rename to src/components/tables/CellGenericFormat.jsx
index d78f7759dcd4..465a7dd25f37 100644
--- a/src/components/tables/CellGenericFormat.js
+++ b/src/components/tables/CellGenericFormat.jsx
@@ -5,8 +5,9 @@ import {
faCheckCircle,
faExclamationCircle,
} from '@fortawesome/free-solid-svg-icons'
-import { CellBadge } from 'src/components/tables'
import { CBadge, CTooltip } from '@coreui/react'
+import CellBoolean from 'src/components/tables/CellBoolean.jsx'
+import cellTable from './CellTable'
const IconWarning = () =>
const IconError = () =>
@@ -20,43 +21,6 @@ function nocolour(iscolourless, content) {
return content
}
-export default function CellBoolean({
- cell,
- warning = false,
- reverse = false,
- colourless = false,
- noDataIsFalse = false,
-}) {
- let normalized = cell
- if (typeof cell === 'boolean') {
- normalized = cell
- } else if (typeof cell === 'string') {
- if (
- cell.toLowerCase() === 'success' ||
- cell.toLowerCase() === 'pass' ||
- cell.toLowerCase() === 'true'
- ) {
- normalized = true
- } else if (cell.toLowerCase() === 'fail' || cell.toLowerCase() === 'false') {
- normalized = false
- }
- }
-
- if (cell === '' && !noDataIsFalse) {
- return
- } else if (colourless && warning && reverse) {
- return nocolour(colourless, normalized ? : )
- } else if (!reverse && !warning) {
- return nocolour(colourless, normalized ? : )
- } else if (!reverse && warning) {
- return nocolour(colourless, normalized ? : )
- } else if (reverse && !warning) {
- return nocolour(colourless, normalized ? : )
- } else if (reverse && warning) {
- return nocolour(colourless, normalized ? : )
- }
-}
-
export function CellTip(cell, overflow = false) {
return (
@@ -67,6 +31,7 @@ export function CellTip(cell, overflow = false) {
export const cellGenericFormatter =
({ warning = false, reverse = false, colourless = true, noDataIsFalse } = {}) =>
+ // eslint-disable-next-line react/display-name
(row, index, column, id) => {
const cell = column.selector(row)
if (cell === null || cell === undefined || cell.length === 0) {
@@ -80,7 +45,11 @@ export const cellGenericFormatter =
return {CellTip('Failed to retrieve from API')}
}
if (cell.toLowerCase().startsWith('http')) {
- return URL
+ return (
+
+ URL
+
+ )
}
return CellTip(cell)
}
@@ -88,6 +57,7 @@ export const cellGenericFormatter =
return {CellTip(cell)}
}
if (Array.isArray(cell) || typeof cell === 'object') {
- return CellTip(JSON.stringify(cell))
+ //return CellTip(JSON.stringify(cell))
+ return cellTable(row, cell)
}
}
diff --git a/src/components/tables/CellLicense.js b/src/components/tables/CellLicense.jsx
similarity index 100%
rename from src/components/tables/CellLicense.js
rename to src/components/tables/CellLicense.jsx
diff --git a/src/components/tables/CellLogo.js b/src/components/tables/CellLogo.jsx
similarity index 100%
rename from src/components/tables/CellLogo.js
rename to src/components/tables/CellLogo.jsx
diff --git a/src/components/tables/CellMathFormatter.jsx b/src/components/tables/CellMathFormatter.jsx
new file mode 100644
index 000000000000..590d180d8641
--- /dev/null
+++ b/src/components/tables/CellMathFormatter.jsx
@@ -0,0 +1,59 @@
+import React from 'react'
+import { CBadge, CTooltip } from '@coreui/react'
+import CellBoolean from 'src/components/tables/CellBoolean.jsx'
+import cellTable from './CellTable'
+
+export function CellTip(cell, overflow = false) {
+ return (
+
+ {String(cell)}
+
+ )
+}
+export const cellMathFormatter =
+ ({ col } = {}) =>
+ (row) => {
+ const evaluateCalculation = (calculation, row) => {
+ try {
+ const formattedCalculation = calculation.replace(/\b\w+(\.\w+|\[\d+\])*\b/g, (key) => {
+ if (!isNaN(key)) {
+ return parseFloat(key)
+ }
+
+ const path = key.split(/\.|\[(\d+)\]/).filter(Boolean) // Splits keys and array indices
+ let currentObject = row
+ for (const prop of path) {
+ if (currentObject && prop in currentObject) {
+ currentObject = currentObject[prop]
+ } else if (!isNaN(prop)) {
+ // Checks if the prop is an array index
+ currentObject = currentObject[parseInt(prop, 10)]
+ } else {
+ throw new Error(`Property '${prop}' not found in row`)
+ }
+ }
+
+ return parseFloat(currentObject)
+ })
+
+ return Number(eval(formattedCalculation))
+ } catch (e) {
+ console.error(e)
+ return null
+ }
+ }
+
+ const result = evaluateCalculation(col.value, row)
+
+ if (result === null) {
+ return 'N/A'
+ }
+
+ if (col.showAs === 'percentage') {
+ return `${result.toFixed(2)}%`
+ } else {
+ return result.toFixed(2)
+ }
+ }
+
+export default cellMathFormatter
diff --git a/src/components/tables/CellNullText.js b/src/components/tables/CellNullText.jsx
similarity index 100%
rename from src/components/tables/CellNullText.js
rename to src/components/tables/CellNullText.jsx
diff --git a/src/components/tables/CellProgressBar.js b/src/components/tables/CellProgressBar.jsx
similarity index 100%
rename from src/components/tables/CellProgressBar.js
rename to src/components/tables/CellProgressBar.jsx
diff --git a/src/components/tables/CellTable.js b/src/components/tables/CellTable.jsx
similarity index 54%
rename from src/components/tables/CellTable.js
rename to src/components/tables/CellTable.jsx
index c874a6854920..6da1d1339b93 100644
--- a/src/components/tables/CellTable.js
+++ b/src/components/tables/CellTable.jsx
@@ -1,9 +1,8 @@
import React from 'react'
import { CButton } from '@coreui/react'
import { ModalService } from '../utilities'
-import { CBadge } from '@coreui/react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
-import { faCheckCircle, faTimesCircle } from '@fortawesome/free-solid-svg-icons' // 1. Import the required FontAwesome icon
+import { faCheckCircle, faTimesCircle } from '@fortawesome/free-solid-svg-icons'
import { cellGenericFormatter } from './CellGenericFormat'
export default function cellTable(
@@ -12,10 +11,31 @@ export default function cellTable(
propertyName,
checkWhenZero = false,
crossWhenZero = false,
+ dangerButton = false, // Added 4th parameter for btn-danger class
) {
- const handleTable = ({ row }) => {
+ var columnProp = ''
+ if (propertyName) {
+ columnProp = row[propertyName]
+ } else {
+ columnProp = column
+ }
+
+ if (!Array.isArray(columnProp) && typeof columnProp === 'object') {
+ columnProp = Object.entries(columnProp).map((row) => {
+ return { Name: row[0], Value: row[1] }
+ })
+ } else if (Array.isArray(columnProp) && typeof Object.entries(columnProp)[0][1] !== 'object') {
+ columnProp = columnProp.map((row) => {
+ return {
+ Value: row,
+ }
+ })
+ }
+
+ const handleTable = ({ columnProp }) => {
const QueryColumns = []
- const columns = Object.keys(row[propertyName][0]).map((key) => {
+
+ const columns = Object.keys(columnProp[0]).map((key) => {
QueryColumns.push({
name: key,
selector: (row) => row[key],
@@ -24,8 +44,9 @@ export default function cellTable(
cell: cellGenericFormatter(),
})
})
+
ModalService.open({
- data: row[propertyName],
+ data: columnProp,
componentType: 'table',
componentProps: {
columns: QueryColumns,
@@ -35,16 +56,16 @@ export default function cellTable(
size: 'lg',
})
}
- //if the row propertyName is a bool, then return a check or cross
- if (typeof row[propertyName] === 'boolean') {
- if (row[propertyName]) {
+
+ if (typeof columnProp === 'boolean') {
+ if (columnProp) {
return
}
return
}
- if (!row[propertyName] || !Array.isArray(row[propertyName]) || row[propertyName].length === 0) {
- if (row[propertyName] === undefined) {
+ if (!columnProp || !Array.isArray(columnProp) || columnProp.length === 0) {
+ if (columnProp === undefined) {
return
}
if (checkWhenZero) {
@@ -56,15 +77,22 @@ export default function cellTable(
return
}
+ // Use dangerButton to determine button class
+ const buttonClassName = dangerButton ? 'btn-danger' : ''
return (
- handleTable({ row })}>
- {row[propertyName].length} Items
+ handleTable({ columnProp })}
+ >
+ {columnProp.length} Items
)
}
export const cellTableFormatter =
- (propertyName, checkWhenZero = false, crossWhenZero = false) =>
+ (propertyName, checkWhenZero = false, crossWhenZero = false, dangerButton = false) =>
(row, index, column, id) => {
- return cellTable(row, column, propertyName, checkWhenZero, crossWhenZero)
+ return cellTable(row, column, propertyName, checkWhenZero, crossWhenZero, dangerButton)
}
diff --git a/src/components/tables/CellTip.js b/src/components/tables/CellTip.jsx
similarity index 100%
rename from src/components/tables/CellTip.js
rename to src/components/tables/CellTip.jsx
diff --git a/src/components/tables/CippDatatable.js b/src/components/tables/CippDatatable.jsx
similarity index 100%
rename from src/components/tables/CippDatatable.js
rename to src/components/tables/CippDatatable.jsx
diff --git a/src/components/tables/CippOffcanvasTable.js b/src/components/tables/CippOffcanvasTable.jsx
similarity index 90%
rename from src/components/tables/CippOffcanvasTable.js
rename to src/components/tables/CippOffcanvasTable.jsx
index fe2f7e0e1c7c..b990c28366cb 100644
--- a/src/components/tables/CippOffcanvasTable.js
+++ b/src/components/tables/CippOffcanvasTable.jsx
@@ -10,7 +10,7 @@ export default function CippOffcanvasTable({ rows }) {
))
return (
-
+
{tableRows}
)
diff --git a/src/components/tables/CippTable.js b/src/components/tables/CippTable.jsx
similarity index 66%
rename from src/components/tables/CippTable.js
rename to src/components/tables/CippTable.jsx
index 75106ab21de4..5effb0f2b538 100644
--- a/src/components/tables/CippTable.js
+++ b/src/components/tables/CippTable.jsx
@@ -1,4 +1,4 @@
-import React, { useRef, useMemo, useState, useCallback } from 'react'
+import React, { useRef, useMemo, useState, useCallback, useEffect } from 'react'
import { useSelector } from 'react-redux'
import { ExportCsvButton, ExportPDFButton } from 'src/components/buttons'
import {
@@ -15,17 +15,28 @@ import {
CModalTitle,
CCallout,
CFormSelect,
+ CAccordion,
+ CAccordionHeader,
+ CAccordionBody,
+ CAccordionItem,
} from '@coreui/react'
import DataTable, { createTheme } from 'react-data-table-component'
import PropTypes from 'prop-types'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
-import { faCheck, faColumns, faSearch, faSync, faTasks } from '@fortawesome/free-solid-svg-icons'
-import { useEffect } from 'react'
+import {
+ faCheck,
+ faColumns,
+ faFileCsv,
+ faFilePdf,
+ faSearch,
+ faSync,
+ faTasks,
+} from '@fortawesome/free-solid-svg-icons'
import { cellGenericFormatter } from './CellGenericFormat'
import { ModalService } from '../utilities'
import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app'
import { ConfirmModal } from '../utilities/SharedModal'
-import { debounce } from 'lodash'
+import { debounce } from 'lodash-es'
import { useSearchParams } from 'react-router-dom'
const FilterComponent = ({ filterText, onFilter, onClear, filterlist, onFilterPreset }) => (
@@ -144,46 +155,19 @@ export default function CippTable({
const [genericGetRequest, getResults] = useLazyGenericGetRequestQuery()
const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
const [getDrowndownInfo, dropDownInfo] = useLazyGenericGetRequestQuery()
- const [modalContent, setModalContent] = useState(null)
+ const [modalContent, setModalContent] = useState({})
//get the search params called "tableFilter" and set the filter to that.
const [searchParams, setSearchParams] = useSearchParams()
- if (searchParams.get('tableFilter') && !filterviaURL && !isModal) {
+ if (
+ searchParams.get('tableFilter') &&
+ (!filterviaURL || searchParams.get('updateTableFilter')) &&
+ !isModal
+ ) {
setFilterText(searchParams.get('tableFilter'))
setFilterviaURL(true)
+ searchParams.delete('updateTableFilter')
}
- useEffect(() => {
- if (dropDownInfo.isFetching) {
- handleModal(
- ,
- modalContent.item.modalUrl,
- modalContent.item.modalType,
- modalContent.item.modalBody,
- modalContent.item.modalInput,
- modalContent.item.modalDropdown,
- )
- }
- if (dropDownInfo.isSuccess) {
- console.log(modalContent)
- handleModal(
- modalContent.item.modalMessage,
- modalContent.item.modalUrl,
- modalContent.item.modalType,
- modalContent.item.modalBody,
- modalContent.item.modalInput,
- modalContent.item.modalDropdown,
- )
- } else if (dropDownInfo.isError) {
- handleModal(
- 'Error connecting to the API.',
- modalContent.item.modalUrl,
- modalContent.item.modalType,
- modalContent.item.modalBody,
- modalContent.item.modalInput,
- modalContent.item.modalDropdown,
- )
- }
- }, [dropDownInfo])
const handleSelectedChange = ({ selectedRows }) => {
setSelectedRows(selectedRows)
if (selectedRows.length < 1) {
@@ -191,21 +175,24 @@ export default function CippTable({
}
}
const [resetPaginationToggle, setResetPaginationToggle] = React.useState(false)
+
// 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 setGraphFilter = useCallback(
+ (e) => {
+ if (graphFilterFunction) {
+ graphFilterFunction(e)
+ }
+ },
+ [graphFilterFunction],
+ )
const debounceSetGraphFilter = useMemo(() => {
return debounce(setGraphFilter, 1000)
- }, [])
+ }, [setGraphFilter])
const debounceSetSearchParams = useCallback(() => {
const currentUrl = new URL(window.location.href)
@@ -250,7 +237,6 @@ export default function CippTable({
//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) {
@@ -288,7 +274,7 @@ export default function CippTable({
if (columns !== updatedColumns) {
setUpdatedColumns(columns)
}
- }, [updatedColumns])
+ }, [columns, updatedColumns])
createTheme(
'cyberdrain',
@@ -334,95 +320,140 @@ export default function CippTable({
},
},
}
- const handleModal = (
- modalMessage,
- modalUrl,
- modalType = 'GET',
- modalBody,
- modalInput,
- modalDropdown,
- ) => {
- if (modalType === 'GET') {
- ModalService.confirm({
- body: (
-
- ),
- title: 'Confirm',
- onConfirm: async () => {
- const resultsarr = []
- for (const row of selectedRows) {
- setLoopRunning(true)
- const urlParams = new URLSearchParams(modalUrl.split('?')[1])
- for (let [paramName, paramValue] of urlParams.entries()) {
- if (paramValue.startsWith('!')) {
- urlParams.set(paramName, row[paramValue.replace('!', '')])
+ const handleModal = useCallback(
+ (modalMessage, modalUrl, modalType = 'GET', modalBody, modalInput, modalDropdown) => {
+ if (modalType === 'GET') {
+ ModalService.confirm({
+ body: (
+
+ ),
+ title: 'Confirm',
+ onConfirm: async () => {
+ const resultsarr = []
+ for (const row of selectedRows) {
+ setLoopRunning(true)
+ const urlParams = new URLSearchParams(modalUrl.split('?')[1])
+ for (let [paramName, paramValue] of urlParams.entries()) {
+ if (paramValue.startsWith('!')) {
+ urlParams.set(paramName, row[paramValue.replace('!', '')])
+ }
}
+ const NewModalUrl = `${modalUrl.split('?')[0]}?${urlParams.toString()}`
+ const results = await genericGetRequest({ path: NewModalUrl, refreshParam: row.id })
+ resultsarr.push(results)
+ setMassResults(resultsarr)
}
- const NewModalUrl = `${modalUrl.split('?')[0]}?${urlParams.toString()}`
- const results = await genericGetRequest({ path: NewModalUrl, refreshParam: row.id })
- resultsarr.push(results)
- setMassResults(resultsarr)
- }
- setLoopRunning(false)
- },
- })
- } else {
- ModalService.confirm({
- body: (
-
- {modalInput && (
-
-
-
- )}
- {modalDropdown && (
-
- {dropDownInfo.isSuccess && (
- ({
- value: data[modalDropdown.valueField],
- label: data[modalDropdown.labelField],
- }))}
- />
- )}
-
- )}
-
{modalMessage}
-
- ),
- title: 'Confirm',
- onConfirm: async () => {
- const resultsarr = []
- for (const row of selectedRows) {
- setLoopRunning(true)
- const urlParams = new URLSearchParams(modalUrl.split('?')[1])
- for (let [paramName, paramValue] of urlParams.entries()) {
- if (paramValue.toString().startsWith('!')) {
- urlParams.set(paramName, row[paramValue.replace('!', '')])
+ setLoopRunning(false)
+ },
+ })
+ } else {
+ ModalService.confirm({
+ body: (
+
+ {modalInput && (
+
+
+
+ )}
+ {modalDropdown && (
+
+ {dropDownInfo.isSuccess && (
+ ({
+ value: data[modalDropdown.valueField],
+ label: data[modalDropdown.labelField],
+ }))}
+ />
+ )}
+
+ )}
+
{modalMessage}
+
+ ),
+ title: 'Confirm',
+ onConfirm: async () => {
+ const resultsarr = []
+ for (const row of selectedRows) {
+ setLoopRunning(true)
+ const urlParams = new URLSearchParams(modalUrl.split('?')[1])
+ for (let [paramName, paramValue] of urlParams.entries()) {
+ if (paramValue.toString().startsWith('!')) {
+ urlParams.set(paramName, row[paramValue.replace('!', '')])
+ }
}
- }
- const newModalBody = {}
- for (let [objName, objValue] of Object.entries(modalBody)) {
- if (objValue.toString().startsWith('!')) {
- newModalBody[objName] = row[objValue.replace('!', '')]
+ const newModalBody = {}
+ for (let [objName, objValue] of Object.entries(modalBody)) {
+ if (objValue.toString().startsWith('!')) {
+ newModalBody[objName] = row[objValue.replace('!', '')]
+ }
}
+ const NewModalUrl = `${modalUrl.split('?')[0]}?${urlParams.toString()}`
+ const results = await genericPostRequest({
+ path: NewModalUrl,
+ values: { ...modalBody, ...newModalBody, ...{ input: inputRef.current.value } },
+ })
+ resultsarr.push(results)
+ setMassResults(resultsarr)
}
- const NewModalUrl = `${modalUrl.split('?')[0]}?${urlParams.toString()}`
- const results = await genericPostRequest({
- path: NewModalUrl,
- values: { ...modalBody, ...newModalBody, ...{ input: inputRef.current.value } },
- })
- resultsarr.push(results)
- setMassResults(resultsarr)
- }
- setLoopRunning(false)
- },
- })
+ setLoopRunning(false)
+ },
+ })
+ }
+ },
+ [
+ dropDownInfo?.data,
+ dropDownInfo?.isSuccess,
+ genericGetRequest,
+ genericPostRequest,
+ selectedRows,
+ ],
+ )
+
+ useEffect(() => {
+ if (dropDownInfo.isFetching) {
+ handleModal(
+ ,
+ modalContent.item.modalUrl,
+ modalContent.item.modalType,
+ modalContent.item.modalBody,
+ modalContent.item.modalInput,
+ modalContent.item.modalDropdown,
+ )
}
- }
+ if (dropDownInfo.isSuccess) {
+ //console.log(modalContent)
+ handleModal(
+ modalContent.item.modalMessage,
+ modalContent.item.modalUrl,
+ modalContent.item.modalType,
+ modalContent.item.modalBody,
+ modalContent.item.modalInput,
+ modalContent.item.modalDropdown,
+ )
+ } else if (dropDownInfo.isError) {
+ handleModal(
+ 'Error connecting to the API.',
+ modalContent.item.modalUrl,
+ modalContent.item.modalType,
+ modalContent.item.modalBody,
+ modalContent.item.modalInput,
+ modalContent.item.modalDropdown,
+ )
+ }
+ }, [
+ dropDownInfo,
+ handleModal,
+ modalContent.item?.modalBody,
+ modalContent.item?.modalDropdown,
+ modalContent.item?.modalInput,
+ modalContent.item?.modalMessage,
+ modalContent.item?.modalType,
+ modalContent.item?.modalUrl,
+ ])
+
const subHeaderComponentMemo = React.useMemo(() => {
const handleClear = () => {
if (filterText) {
@@ -458,6 +489,7 @@ export default function CippTable({
if (refreshFunction) {
defaultActions.push([
{
refreshFunction((Math.random() + 1).toString(36).substring(7))
}}
@@ -492,9 +524,12 @@ export default function CippTable({
keys.reduce((acc, curr) => {
const key = curr.split('/')
if (key.length > 1) {
- var property = obj
- for (var x = 0; x < key.length; x++) {
- if (property.hasOwnProperty(key[x]) && property[key[x]] !== null) {
+ let property = obj
+ for (let x = 0; x < key.length; x++) {
+ if (
+ Object.prototype.hasOwnProperty.call(property, key[x]) &&
+ property[key[x]] !== null
+ ) {
property = property[key[x]]
} else {
property = 'n/a'
@@ -557,7 +592,7 @@ export default function CippTable({
}
defaultActions.push([
-
+
,
+
+
+
+
+
+ {dataKeys() && (
+ <>
+
+ >
+ )}
+
+ ,
])
}
if (!disableCSVExport) {
defaultActions.push([
- ,
+ <>
+
+
+
+
+
+ {dataKeys() && (
+ <>
+
+
+
+
+
+
+ >
+ )}
+
+
+ >,
])
}
}
@@ -634,6 +721,7 @@ export default function CippTable({
setFilterText(e.target.value)}
onFilterPreset={(e) => {
+ if (e === '') setGraphFilter('')
setFilterText(e)
}}
onClear={handleClear}
@@ -647,6 +735,7 @@ export default function CippTable({
)
}, [
actions,
+ selectedRows,
disablePDFExport,
disableCSVExport,
filterText,
@@ -655,8 +744,11 @@ export default function CippTable({
data,
columns,
reportName,
+ selectedRows,
+ filteredItems,
])
const tablePageSize = useSelector((state) => state.app.tablePageSize)
+
return (
{!isFetching && error &&
Error loading data }
@@ -665,12 +757,41 @@ export default function CippTable({
<>
{(massResults.length >= 1 || loopRunning) && (
- {massResults.map((message, idx) => {
- const results = message.data?.Results
- const displayResults = Array.isArray(results) ? results.join(', ') : results
-
- return {displayResults}
- })}
+ {massResults[0]?.data?.Metadata?.Heading && (
+
+ {massResults.map((message, idx) => {
+ const results = message.data?.Results
+ const displayResults = Array.isArray(results)
+ ? results.join('')
+ : 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 (
+
+
+
+ {message.data?.Metadata?.Heading}
+
+
+ {results.map((line, i) => {
+ return {line}
+ })}
+
+
+ )
+ })}
+
+ )}
+ {!massResults[0]?.data?.Metadata?.Heading &&
+ massResults.map((message, idx) => {
+ const results = message.data?.Results
+ const displayResults = Array.isArray(results) ? results.join(', ') : results
+ return {displayResults}
+ })}
{loopRunning && (
@@ -721,8 +842,34 @@ export default function CippTable({
export const CippTablePropTypes = {
reportName: PropTypes.string.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
- keyField: PropTypes.string,
- tableProps: PropTypes.object,
+ refreshFunction: PropTypes.func,
+ graphFilterFunction: PropTypes.func,
+ dynamicColumns: PropTypes.bool,
+ defaultFilterText: PropTypes.string,
+ isModal: PropTypes.bool,
+ exportFiltered: PropTypes.bool,
+ showFilter: PropTypes.bool,
+ tableProps: PropTypes.shape({
+ keyField: PropTypes.string,
+ theme: PropTypes.string,
+ pagination: PropTypes.bool,
+ responsive: PropTypes.bool,
+ dense: PropTypes.bool,
+ striped: PropTypes.bool,
+ subheader: PropTypes.bool,
+ // @TODO
+ // expandableRows,
+ // actionsList,
+ // expandableRowsComponent,
+ // expandableRowsHideExpander,
+ // expandOnRowClicked,
+ // selectableRows,
+ sortFunction: PropTypes.bool,
+ onSelectedRowsChange: PropTypes.func,
+ highlightOnHover: PropTypes.bool,
+ disableDefaultActions: PropTypes.bool,
+ actions: PropTypes.arrayOf(PropTypes.node),
+ }),
data: PropTypes.array,
isFetching: PropTypes.bool,
disablePDFExport: PropTypes.bool,
diff --git a/src/components/tables/WizardTableField.js b/src/components/tables/WizardTableField.jsx
similarity index 88%
rename from src/components/tables/WizardTableField.js
rename to src/components/tables/WizardTableField.jsx
index 6dc929edb908..bb6ba35a9da1 100644
--- a/src/components/tables/WizardTableField.js
+++ b/src/components/tables/WizardTableField.jsx
@@ -13,6 +13,7 @@ export default class WizardTableField extends React.Component {
reportName: PropTypes.string.isRequired,
keyField: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
+ params: PropTypes.object,
columns: PropTypes.array.isRequired,
fieldProps: PropTypes.object,
}
@@ -22,10 +23,10 @@ export default class WizardTableField extends React.Component {
columns: [],
}
- handleSelect = ({ selectedRows }) => {
+ handleSelect = ({ selectedRows = [] }) => {
// console.log(selectedRows)
const { fieldProps, keyField } = this.props
- if (selectedRows !== []) {
+ if (selectedRows.length > 0) {
fieldProps.input.onChange(selectedRows)
this.setState(() => ({
selected: selectedRows.map((el) => el[keyField]),
@@ -56,7 +57,7 @@ export default class WizardTableField extends React.Component {
}
render() {
- const { reportName, keyField, columns, path } = this.props
+ const { reportName, keyField, columns, path, params, ...props } = this.props
return (
)
}
diff --git a/src/components/utilities/CippActionsOffcanvas.js b/src/components/utilities/CippActionsOffcanvas.jsx
similarity index 68%
rename from src/components/utilities/CippActionsOffcanvas.js
rename to src/components/utilities/CippActionsOffcanvas.jsx
index ce148defa5a4..3cec2692a969 100644
--- a/src/components/utilities/CippActionsOffcanvas.js
+++ b/src/components/utilities/CippActionsOffcanvas.jsx
@@ -1,10 +1,11 @@
-import React, { useRef } from 'react'
+import React, { useRef, useEffect, useState, useCallback } from 'react'
import PropTypes from 'prop-types'
import {
CButton,
CCallout,
CCard,
CCardBody,
+ CCardHeader,
CCardText,
CCardTitle,
CFormInput,
@@ -21,16 +22,106 @@ import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 's
import { Link, useNavigate } from 'react-router-dom'
import { stringCamelCase } from 'src/components/utilities/CippCamelCase'
import ReactTimeAgo from 'react-time-ago'
-import { useEffect } from 'react'
-import { useState } from 'react'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faGlobe } from '@fortawesome/free-solid-svg-icons'
export default function CippActionsOffcanvas(props) {
const inputRef = useRef('')
const [genericGetRequest, getResults] = useLazyGenericGetRequestQuery()
const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
const [getDrowndownInfo, dropDownInfo] = useLazyGenericGetRequestQuery()
- const [modalContent, setModalContent] = useState(null)
+ const [modalContent, setModalContent] = useState({})
+ const handleLink = useNavigate()
+ const handleExternalLink = (link) => {
+ window.open(link, '_blank')
+ }
+ const handleModal = useCallback(
+ (modalMessage, modalUrl, modalType = 'GET', modalBody, modalInput, modalDropdown) => {
+ const handlePostConfirm = () => {
+ const selectedValue = inputRef.current.value
+ console.log(inputRef)
+ let additionalFields = {}
+
+ if (inputRef.current.nodeName === 'SELECT') {
+ const selectedItem = dropDownInfo.data.find(
+ (item) => item[modalDropdown.valueField] === selectedValue,
+ )
+ if (selectedItem && modalDropdown.addedField) {
+ Object.keys(modalDropdown.addedField).forEach((key) => {
+ additionalFields[key] = selectedItem[modalDropdown.addedField[key]]
+ })
+ }
+ }
+ const postRequestBody = {
+ ...modalBody,
+ ...additionalFields,
+ input: selectedValue,
+ }
+ // Send the POST request
+ genericPostRequest({
+ path: modalUrl,
+ values: postRequestBody,
+ })
+ }
+
+ // Modal setup for GET, codeblock, and other types
+ if (modalType === 'GET') {
+ ModalService.confirm({
+ body: (
+
+ ),
+ title: 'Confirm',
+ onConfirm: () => genericGetRequest({ path: modalUrl }),
+ })
+ } else if (modalType === 'codeblock') {
+ ModalService.open({
+ data: modalBody,
+ componentType: 'codeblock',
+ title: 'Info',
+ size: 'lg',
+ })
+ } else {
+ ModalService.confirm({
+ key: modalContent,
+ body: (
+
+ {modalInput && (
+
+
+
+ )}
+ {modalDropdown && (
+
+ {dropDownInfo.isSuccess && (
+ ({
+ value: data[modalDropdown.valueField],
+ label: data[modalDropdown.labelField],
+ }))}
+ />
+ )}
+
+ )}
+
{modalMessage}
+
+ ),
+ title: 'Confirm',
+ onConfirm: handlePostConfirm,
+ })
+ }
+ },
+ [
+ dropDownInfo?.data,
+ dropDownInfo?.isSuccess,
+ genericGetRequest,
+ genericPostRequest,
+ modalContent,
+ ],
+ )
useEffect(() => {
if (dropDownInfo.isFetching) {
handleModal(
@@ -61,73 +152,17 @@ export default function CippActionsOffcanvas(props) {
modalContent.modalDropdown,
)
}
- }, [dropDownInfo])
+ }, [
+ dropDownInfo,
+ handleModal,
+ modalContent.modalBody,
+ modalContent.modalDropdown,
+ modalContent.modalInput,
+ modalContent.modalMessage,
+ modalContent.modalType,
+ modalContent.modalUrl,
+ ])
- const handleLink = useNavigate()
- const handleExternalLink = (link) => {
- window.open(link, '_blank')
- }
- const handleModal = (
- modalMessage,
- modalUrl,
- modalType = 'GET',
- modalBody,
- modalInput,
- modalDropdown,
- ) => {
- if (modalType === 'GET') {
- ModalService.confirm({
- body: (
-
- ),
- title: 'Confirm',
- onConfirm: () => genericGetRequest({ path: modalUrl }),
- })
- } else if (modalType === 'codeblock') {
- ModalService.open({
- data: modalBody,
- componentType: 'codeblock',
- title: 'Info',
- size: 'lg',
- })
- } else {
- ModalService.confirm({
- key: modalContent,
- body: (
-
- {modalInput && (
-
-
-
- )}
- {modalDropdown && (
-
- {dropDownInfo.isSuccess && (
- ({
- value: data[modalDropdown.valueField],
- label: data[modalDropdown.labelField],
- }))}
- />
- )}
-
- )}
-
{modalMessage}
-
- ),
- title: 'Confirm',
- onConfirm: () => [
- genericPostRequest({
- path: modalUrl,
- values: { ...modalBody, ...{ input: inputRef.current.value } },
- }),
- ],
- })
- }
- }
const handleOnClick = (
link,
modal,
@@ -149,7 +184,14 @@ export default function CippActionsOffcanvas(props) {
if (modalDropdown) {
getDrowndownInfo({ path: modalDropdown.url })
}
- setModalContent({ modalMessage, modalUrl, modalType, modalBody, modalInput, modalDropdown })
+ setModalContent({
+ modalMessage,
+ modalUrl,
+ modalType,
+ modalBody,
+ modalInput,
+ modalDropdown,
+ })
handleModal(modalMessage, modalUrl, modalType, modalBody, modalInput, modalDropdown)
}
@@ -183,7 +225,9 @@ export default function CippActionsOffcanvas(props) {
>
))
- } catch (error) {}
+ } catch (error) {
+ // swallow error
+ }
const extendedInfoContent =
let actionsContent
@@ -273,9 +317,15 @@ export default function CippActionsOffcanvas(props) {
Could not connect to API: {getResults.error.message}
)}
- Extended Information
+
+
+
+ Extended Information
+
+
+ {extendedInfoContent}
+
{cardContent && cardContent}
- {extendedInfoContent}
{Actions }
{actionsContent}
diff --git a/src/components/utilities/CippCamelCase.js b/src/components/utilities/CippCamelCase.jsx
similarity index 100%
rename from src/components/utilities/CippCamelCase.js
rename to src/components/utilities/CippCamelCase.jsx
diff --git a/src/components/utilities/CippCodeBlock.js b/src/components/utilities/CippCodeBlock.jsx
similarity index 100%
rename from src/components/utilities/CippCodeBlock.js
rename to src/components/utilities/CippCodeBlock.jsx
diff --git a/src/components/utilities/CippCodeOffcanvas.js b/src/components/utilities/CippCodeOffcanvas.jsx
similarity index 85%
rename from src/components/utilities/CippCodeOffcanvas.js
rename to src/components/utilities/CippCodeOffcanvas.jsx
index 38a23b81663f..589c27ce3cb8 100644
--- a/src/components/utilities/CippCodeOffcanvas.js
+++ b/src/components/utilities/CippCodeOffcanvas.jsx
@@ -5,10 +5,11 @@ import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 's
import { Editor } from '@monaco-editor/react'
import { useSelector } from 'react-redux'
+import PropTypes from 'prop-types'
function CippCodeOffCanvas({
row,
- state,
+ state: visible,
hideFunction,
type,
title = 'Template JSON',
@@ -27,21 +28,22 @@ function CippCodeOffCanvas({
setInvalid(true)
}
}
+
return (
<>
{
+ 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
diff --git a/src/components/utilities/CippListOffcanvas.js b/src/components/utilities/CippListOffcanvas.jsx
similarity index 100%
rename from src/components/utilities/CippListOffcanvas.js
rename to src/components/utilities/CippListOffcanvas.jsx
diff --git a/src/components/utilities/CippOffcanvas.js b/src/components/utilities/CippOffcanvas.jsx
similarity index 96%
rename from src/components/utilities/CippOffcanvas.js
rename to src/components/utilities/CippOffcanvas.jsx
index 3148f5992ecb..d3eaece82e94 100644
--- a/src/components/utilities/CippOffcanvas.js
+++ b/src/components/utilities/CippOffcanvas.jsx
@@ -45,9 +45,10 @@ export const CippOffcanvasPropTypes = {
placement: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
visible: PropTypes.bool,
- id: PropTypes.string.isRequired,
+ id: PropTypes.string,
hideFunction: PropTypes.func.isRequired,
refreshFunction: PropTypes.func,
+ addedClass: PropTypes.string,
}
CippOffcanvas.propTypes = CippOffcanvasPropTypes
diff --git a/src/components/utilities/CippProfile.js b/src/components/utilities/CippProfile.jsx
similarity index 100%
rename from src/components/utilities/CippProfile.js
rename to src/components/utilities/CippProfile.jsx
diff --git a/src/components/utilities/CippTenantOffcanvas.js b/src/components/utilities/CippTenantOffcanvas.jsx
similarity index 96%
rename from src/components/utilities/CippTenantOffcanvas.js
rename to src/components/utilities/CippTenantOffcanvas.jsx
index 79f249452f6e..0538ecbf4344 100644
--- a/src/components/utilities/CippTenantOffcanvas.js
+++ b/src/components/utilities/CippTenantOffcanvas.jsx
@@ -6,6 +6,7 @@ import { CippActionsOffcanvas } from 'src/components/utilities'
import { useLazyGenericGetRequestQuery } from 'src/store/api/app'
import Skeleton from 'react-loading-skeleton'
import Portals from 'src/data/portals'
+import PropTypes from 'prop-types'
export const CippTenantOffcanvasRow = (row, rowIndex, formatExtraData) => {
const tenant = row
@@ -15,6 +16,7 @@ export const CippTenantOffcanvasRow = (row, rowIndex, formatExtraData) => {
function CippTenantOffcanvas({ tenant, buildingIcon = false }) {
const [getTenantDetails, tenantDetails] = useLazyGenericGetRequestQuery()
const [ocVisible, setOCVisible] = useState(false)
+
function loadOffCanvasDetails(domainName) {
setOCVisible(true)
getTenantDetails({ path: `api/ListTenantDetails?tenantfilter=${domainName}` })
@@ -30,6 +32,7 @@ function CippTenantOffcanvas({ tenant, buildingIcon = false }) {
>
)
}
+
const actions = Portals.map((portal) => ({
icon: ,
label: portal.label,
@@ -103,4 +106,9 @@ function CippTenantOffcanvas({ tenant, buildingIcon = false }) {
)
}
+CippTenantOffcanvas.propTypes = {
+ tenant: PropTypes.object,
+ buildingIcon: PropTypes.bool,
+}
+
export default CippTenantOffcanvas
diff --git a/src/components/utilities/ErrorBoundary.js b/src/components/utilities/ErrorBoundary.jsx
similarity index 91%
rename from src/components/utilities/ErrorBoundary.js
rename to src/components/utilities/ErrorBoundary.jsx
index a32fd5e589f0..8240d09b4139 100644
--- a/src/components/utilities/ErrorBoundary.js
+++ b/src/components/utilities/ErrorBoundary.jsx
@@ -28,5 +28,5 @@ export default class ErrorBoundary extends React.Component {
}
ErrorBoundary.propTypes = {
- children: PropTypes.node,
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
}
diff --git a/src/components/utilities/FastSwitcher.js b/src/components/utilities/FastSwitcher.jsx
similarity index 98%
rename from src/components/utilities/FastSwitcher.js
rename to src/components/utilities/FastSwitcher.jsx
index d6d7db811057..5067a1ee2444 100644
--- a/src/components/utilities/FastSwitcher.js
+++ b/src/components/utilities/FastSwitcher.jsx
@@ -114,6 +114,7 @@ const ResultsRow = ({ match = [] }) => {
const navigate = useNavigate()
const dispatch = useDispatch()
+ // eslint-disable-next-line no-unsafe-optional-chaining
const { name, section, icon, to } = match?.obj
const [nameMatch, sectionMatch, toMatch] = match
diff --git a/src/components/utilities/Loading.js b/src/components/utilities/Loading.jsx
similarity index 100%
rename from src/components/utilities/Loading.js
rename to src/components/utilities/Loading.jsx
diff --git a/src/components/utilities/ModalRoot.js b/src/components/utilities/ModalRoot.jsx
similarity index 100%
rename from src/components/utilities/ModalRoot.js
rename to src/components/utilities/ModalRoot.jsx
diff --git a/src/components/utilities/PageSizeSwitcher.js b/src/components/utilities/PageSizeSwitcher.jsx
similarity index 100%
rename from src/components/utilities/PageSizeSwitcher.js
rename to src/components/utilities/PageSizeSwitcher.jsx
diff --git a/src/components/utilities/PrivateRoute.js b/src/components/utilities/PrivateRoute.jsx
similarity index 92%
rename from src/components/utilities/PrivateRoute.js
rename to src/components/utilities/PrivateRoute.jsx
index 98c9b7440d4d..b89f0d5aa8ee 100644
--- a/src/components/utilities/PrivateRoute.js
+++ b/src/components/utilities/PrivateRoute.jsx
@@ -15,10 +15,10 @@ export const PrivateRoute = ({ children, routeType }) => {
}
dispatch(updateAccessToken(profile))
- let roles
- if (null !== profile.clientPrincipal) {
+ let roles = null
+ if (null !== profile?.clientPrincipal) {
roles = profile?.clientPrincipal.userRoles
- } else if (null === profile.clientPrincipal) {
+ } else if (null === profile?.clientPrincipal) {
return
}
if (null === roles) {
diff --git a/src/components/utilities/ReportImage.js b/src/components/utilities/ReportImage.jsx
similarity index 100%
rename from src/components/utilities/ReportImage.js
rename to src/components/utilities/ReportImage.jsx
diff --git a/src/components/utilities/SharedModal.js b/src/components/utilities/SharedModal.jsx
similarity index 100%
rename from src/components/utilities/SharedModal.js
rename to src/components/utilities/SharedModal.jsx
diff --git a/src/components/utilities/StatusIcon.js b/src/components/utilities/StatusIcon.jsx
similarity index 100%
rename from src/components/utilities/StatusIcon.js
rename to src/components/utilities/StatusIcon.jsx
diff --git a/src/components/utilities/TenantListSelector.js b/src/components/utilities/TenantListSelector.jsx
similarity index 100%
rename from src/components/utilities/TenantListSelector.js
rename to src/components/utilities/TenantListSelector.jsx
diff --git a/src/components/utilities/TenantSelector.js b/src/components/utilities/TenantSelector.jsx
similarity index 70%
rename from src/components/utilities/TenantSelector.js
rename to src/components/utilities/TenantSelector.jsx
index 570f2f30c002..3abcc5b9c46f 100644
--- a/src/components/utilities/TenantSelector.js
+++ b/src/components/utilities/TenantSelector.jsx
@@ -4,7 +4,7 @@ import { useDispatch, useSelector } from 'react-redux'
import PropTypes from 'prop-types'
import { useListTenantsQuery } from 'src/store/api/tenants'
import { setCurrentTenant } from 'src/store/features/app'
-import { CDropdown, CDropdownMenu, CDropdownToggle } from '@coreui/react'
+import { CButton, CDropdown, CDropdownMenu, CDropdownToggle } from '@coreui/react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { queryString } from 'src/helpers'
import { faBuilding } from '@fortawesome/free-solid-svg-icons'
@@ -77,36 +77,28 @@ const TenantSelector = ({ action, showAllTenantSelector = true, NavSelector = fa
<>
{NavSelector && (
<>
-
-
- {currentTenant?.customerId !== 'AllTenants' ? (
-
- ) : (
-
- )}
- {currentTenant?.defaultDomainName ? (
- <>
- {currentTenant.displayName}
- >
- ) : (
- placeholder
- )}
-
-
- ({
- value: customerId,
- name: `${displayName} (${defaultDomainName})`,
- }))}
- />
-
-
+ {currentTenant?.customerId !== 'AllTenants' ? (
+
+ ) : (
+
+
+
+ )}
+
+ ({
+ value: customerId,
+ name: `${displayName} (${defaultDomainName})`,
+ }))}
+ />
+
>
)}
{!NavSelector && (
diff --git a/src/components/utilities/TenantSelectorMultiple.js b/src/components/utilities/TenantSelectorMultiple.jsx
similarity index 100%
rename from src/components/utilities/TenantSelectorMultiple.js
rename to src/components/utilities/TenantSelectorMultiple.jsx
diff --git a/src/components/utilities/ThemeSwitcher.js b/src/components/utilities/ThemeSwitcher.jsx
similarity index 100%
rename from src/components/utilities/ThemeSwitcher.js
rename to src/components/utilities/ThemeSwitcher.jsx
diff --git a/src/components/utilities/Toasts.js b/src/components/utilities/Toasts.jsx
similarity index 100%
rename from src/components/utilities/Toasts.js
rename to src/components/utilities/Toasts.jsx
diff --git a/src/components/utilities/UniversalSearch.js b/src/components/utilities/UniversalSearch.jsx
similarity index 100%
rename from src/components/utilities/UniversalSearch.js
rename to src/components/utilities/UniversalSearch.jsx
diff --git a/src/components/utilities/UsageLocation.js b/src/components/utilities/UsageLocation.jsx
similarity index 100%
rename from src/components/utilities/UsageLocation.js
rename to src/components/utilities/UsageLocation.jsx
diff --git a/src/components/utilities/index.js b/src/components/utilities/index.js
index c56c4bac84e0..cf22133abbbc 100644
--- a/src/components/utilities/index.js
+++ b/src/components/utilities/index.js
@@ -1,4 +1,5 @@
import CippActionsOffcanvas from 'src/components/utilities/CippActionsOffcanvas'
+import CippCodeOffCanvas from 'src/components/utilities/CippCodeOffcanvas.jsx'
import CippCodeBlock from 'src/components/utilities/CippCodeBlock'
import CippOffcanvas from 'src/components/utilities/CippOffcanvas'
import CippProfile from 'src/components/utilities/CippProfile'
@@ -19,6 +20,7 @@ import UsageLocation from 'src/components/utilities/UsageLocation'
export {
CippActionsOffcanvas,
CippCodeBlock,
+ CippCodeOffCanvas,
CippOffcanvas,
CippProfile,
ErrorBoundary,
diff --git a/src/data/BPAField.schema.v1.json b/src/data/BPAField.schema.v1.json
index 56a6a07fd1de..810329db4e16 100644
--- a/src/data/BPAField.schema.v1.json
+++ b/src/data/BPAField.schema.v1.json
@@ -87,6 +87,10 @@
{
"const": "number",
"title": "Displays as a numerical value"
+ },
+ {
+ "const": "math",
+ "title": "Displays as a calculated value"
}
]
}
diff --git a/src/data/Extensions.json b/src/data/Extensions.json
index 7d7703f4d13a..4d31c1df3dbc 100644
--- a/src/data/Extensions.json
+++ b/src/data/Extensions.json
@@ -106,5 +106,55 @@
"label": "Enable Integration"
}
]
+ },
+ {
+ "name": "NinjaOne Integration",
+ "type": "NinjaOne",
+ "cat": "Documentation & Monitoring",
+ "forceSyncButton": true,
+ "helpText": "NOTE: This integration requires version 5.6 of NinjaOne, which rolls out regionally between the end of November and mid-December. This integration allows you to populate custom fields with Tenant information, monitor device compliance state, document other items and generate relationships inside NinjaOne.",
+ "SettingOptions": [
+ {
+ "type": "input",
+ "fieldtype": "input",
+ "name": "NinjaOne.Instance",
+ "label": "Please enter your NinjaOne Instance",
+ "placeholder": "app.ninjarmm.com, eu.ninjarmm.com, oc.ninjarmm.com, ca.ninjarmm.com, us2.ninjarmm.com"
+ },
+ {
+ "type": "input",
+ "fieldtype": "password",
+ "name": "NinjaOne.ClientID",
+ "label": "NinjaOne API Client ID",
+ "placeholder": "Enter your NinjaOne API Client ID"
+ },
+ {
+ "type": "input",
+ "fieldtype": "password",
+ "name": "NinjaOne.APIKey",
+ "label": "NinjaOne API Client Secret",
+ "placeholder": "Enter your NinjaOne API Client Secret"
+ },
+ {
+ "type": "checkbox",
+ "name": "NinjaOne.UserDocumentsEnabled",
+ "label": "Synchronize Detailed User Information (Requires NinjaOne Documentation)"
+ },
+ {
+ "type": "checkbox",
+ "name": "NinjaOne.LicenseDocumentsEnabled",
+ "label": "Synchronize Detailed License Information (Requires NinjaOne Documentation)"
+ },
+ {
+ "type": "checkbox",
+ "name": "NinjaOne.LicensedOnly",
+ "label": "Only Synchronize Licensed Users"
+ },
+ {
+ "type": "checkbox",
+ "name": "NinjaOne.Enabled",
+ "label": "Enable Integration"
+ }
+ ]
}
]
diff --git a/src/data/standards.json b/src/data/standards.json
index d85bddf66df7..b5fbfbfeab28 100644
--- a/src/data/standards.json
+++ b/src/data/standards.json
@@ -1,548 +1,780 @@
[
{
- "name": "standards.MailContacts.GeneralContact.Enabled",
- "cat": "Global",
- "helpText": "Receives emails about updates about subscriptions etc",
- "addedComponent": {
- "type": "input",
- "name": "standards.MailContacts.GeneralContact.Mail",
- "label": "General Contact"
+ "name": "standards.MailContacts",
+ "cat": "Global Standards",
+ "helpText": "Defines the email address to receive general updates and information related to M365 subscriptions. Leave a contact field blank if you do not want to update the contact information.",
+ "disabledFeatures": {
+ "report": false,
+ "warn": false,
+ "remediate": false
},
- "label": "Set General Contact e-mail"
- },
- {
- "name": "standards.MailContacts.SecurityContact.Enabled",
- "cat": "Global",
- "helpText": "Receives emails about security alerts or advisories by Microsoft",
- "addedComponent": {
- "type": "input",
- "name": "standards.MailContacts.SecurityContact.Mail",
- "label": "Security Contact"
- },
- "label": "Set Security Contact e-mail"
- },
- {
- "name": "standards.MailContacts.MarketingContact.Enabled",
- "cat": "Global",
- "helpText": "Receives the emails related to marketing; new features etc",
- "addedComponent": {
- "type": "input",
- "name": "standards.MailContacts.MarketingContact.Mail",
- "label": "Marketing Contact"
- },
- "label": "Set Marketing Contact e-mail"
- },
- {
- "name": "standards.MailContacts.TechContact.Enabled",
- "cat": "Global",
- "helpText": "Receives emails related to possible technical issues, service disruptions, etc",
- "addedComponent": {
- "type": "input",
- "name": "standards.MailContacts.TechContact.Mail",
- "label": "Technical Contact"
- },
- "label": "Set Technical Contact e-mail"
+ "addedComponent": [
+ {
+ "type": "input",
+ "name": "standards.MailContacts.GeneralContact",
+ "label": "General Contact"
+ },
+ {
+ "type": "input",
+ "name": "standards.MailContacts.SecurityContact",
+ "label": "Security Contact"
+ },
+ {
+ "type": "input",
+ "name": "standards.MailContacts.MarketingContact",
+ "label": "Marketing Contact"
+ },
+ {
+ "type": "input",
+ "name": "standards.MailContacts.TechContact",
+ "label": "Technical Contact"
+ }
+ ],
+ "label": "Set contact e-mails",
+ "impact": "Low Impact",
+ "impactColour": "info"
},
{
"name": "standards.AuditLog",
- "cat": "Global",
- "helpText": "Also runs Enable-OrganizationCustomization if needed",
- "addedComponent": null,
- "label": "Enable the Unified Audit Log"
+ "cat": "Global Standards",
+ "helpText": "Enables the Unified Audit Log for tracking and auditing activities. Also runs Enable-OrganizationCustomization if necessary.",
+ "addedComponent": [],
+ "label": "Enable the Unified Audit Log",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "name": "standards.PhishProtection",
+ "cat": "Global Standards",
+ "helpText": "Adds branding to the logon page that only appears if the url is not login.microsoftonline.com. This potentially prevents AITM attacks via EvilNginx. This will also automatically generate alerts if a clone of your login page has been found when set to Remediate.",
+ "addedComponent": [],
+ "label": "Enable Phishing Protection system via branding CSS",
+ "impact": "Low Impact",
+ "impactColour": "info",
+ "disabledFeatures": {
+ "report": true,
+ "warn": true,
+ "remediate": false
+ }
+ },
+ {
+ "name": "standards.EnableCustomerLockbox",
+ "cat": "Global Standards",
+ "helpText": "Enables Customer Lockbox that offers an approval process for Microsoft support to access organization data",
+ "addedComponent": [],
+ "label": "Enable Customer Lockbox",
+ "impact": "Low Impact",
+ "impactColour": "info"
},
{
"name": "standards.AnonReportDisable",
- "cat": "Global",
- "helpText": "",
- "addedComponent": null,
- "label": "Enable Usernames instead of pseudo anonymised names in reports"
+ "cat": "Global Standards",
+ "helpText": "Shows usernames instead of pseudo anonymised names in reports. This standard is required for reporting to work correctly.",
+ "addedComponent": [],
+ "label": "Enable Usernames instead of pseudo anonymised names in reports",
+ "impact": "Low Impact",
+ "impactColour": "info"
},
{
"name": "standards.DisableGuestDirectory",
- "cat": "Global",
- "helpText": "",
- "addedComponent": null,
- "label": "Restrict guest user access to directory objects"
- },
- {
- "name": "standards.ModernAuth",
- "cat": "Global",
- "helpText": "Modern Authentication is enabled by default. This standard is no longer required and can be safely disabled",
- "addedComponent": null,
- "label": "Enable Modern Authentication"
- },
- {
- "name": "standards.DisableBasicAuth",
- "cat": "Global",
- "helpText": "Basic Authentication is disabled by default. This standard is no longer required and can be safely disabled",
- "addedComponent": null,
- "label": "Disable Basic Authentication"
+ "cat": "Global Standards",
+ "helpText": "Disables Guest access to enumerate directory objects. This prevents guest users from seeing other users or guests in the directory.",
+ "addedComponent": [],
+ "label": "Restrict guest user access to directory objects",
+ "impact": "Low Impact",
+ "impactColour": "info"
},
{
"name": "standards.DisableBasicAuthSMTP",
- "cat": "Global",
- "helpText": "Disables SMTP AUTH for the organization. This is the default for new tenants. Can be overridden by enabling SMTP AUTH on specific users",
- "addedComponent": null,
- "label": "Disable SMTP Basic Authentication"
+ "cat": "Global Standards",
+ "helpText": "Disables SMTP AUTH for the organization and all users. This is the default for new tenants. ",
+ "addedComponent": [],
+ "label": "Disable SMTP Basic Authentication",
+ "impact": "Medium Impact",
+ "impactColour": "warning"
},
{
- "name": "standards.laps",
- "cat": "AAD",
- "helpText": "Enables the tenant to use LAPS",
- "addedComponent": null,
- "label": "Enable LAPs on the tenant"
+ "name": "standards.ActivityBasedTimeout",
+ "cat": "Global Standards",
+ "helpText": "Enables and sets Idle session timeout for Microsoft 365 to 1 hour. This policy affects most M365 web apps",
+ "addedComponent": [],
+ "label": "Enable 1 hour Activity based Timeout",
+ "impact": "Medium Impact",
+ "impactColour": "warning"
},
{
- "name": "standards.PWnumberMatchingRequiredState",
- "cat": "AAD",
- "helpText": "Passwordless with number matching is now enabled by default. This standard is no longer required and can be safely disabled",
- "addedComponent": null,
- "label": "Enable Passwordless with Number Matching"
+ "name": "standards.laps",
+ "cat": "Entra (AAD) Standards",
+ "helpText": "Enables the tenant to use LAPS. You must still create a policy for LAPS to be active on all devices. Use the template standards to deploy this by default.",
+ "addedComponent": [],
+ "label": "Enable LAPS on the tenant",
+ "impact": "Low Impact",
+ "impactColour": "info"
},
{
- "cat": "AAD",
+ "cat": "Entra (AAD) Standards",
"name": "standards.PWdisplayAppInformationRequiredState",
- "helpText": "Enables the MS authenticator app to display information about the app that is requesting authentication",
- "addedComponent": null,
- "label": "Enable Passwordless with Location information and Number Matching"
- },
- {
- "cat": "AAD",
- "name": "standards.PWcompanionAppAllowedState.Enabled",
- "helpText": "",
- "addedComponent": {
- "type": "Select",
- "label": "Select value",
- "name": "standards.PWcompanionAppAllowedState.state",
- "values": [
- {
- "label": "Enabled",
- "value": "enabled"
- },
- {
- "label": "Disabled",
- "value": "disabled"
- }
- ]
- },
- "label": "Set Authenticator Lite state"
- },
- {
- "cat": "AAD",
- "name": "standards.TAP.Enabled",
- "helpText": "",
- "addedComponent": {
- "type": "Select",
- "label": "Select TAP Lifetime",
- "name": "standards.TAP.config",
- "values": [
- {
- "label": "Only Once",
- "value": "true"
- },
- {
- "label": "Multiple Logons",
- "value": "false"
- }
- ]
- },
- "label": "Enable Temporary Access Passwords"
- },
- {
- "cat": "AAD",
- "name": "standards.SecurityDefaults",
- "helpText": "Enables security defaults for the tenant. This has a lot of implications and should be carefully considered before enabling",
- "addedComponent": null,
- "label": "Enable Security Defaults"
- },
- {
- "cat": "AAD",
+ "helpText": "Enables the MS authenticator app to display information about the app that is requesting authentication. This displays the application name.",
+ "addedComponent": [],
+ "label": "Enable Passwordless with Location information and Number Matching",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.allowOTPTokens",
+ "helpText": "Allows you to use MS authenticator OTP token generator",
+ "addedComponent": [],
+ "label": "Enable OTP via Authenticator",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.PWcompanionAppAllowedState",
+ "helpText": "Sets the state of Authenticator Lite, Authenticator lite is a companion app for passwordless authentication.",
+ "addedComponent": [
+ {
+ "type": "Select",
+ "label": "Select value",
+ "name": "standards.PWcompanionAppAllowedState.state",
+ "values": [
+ {
+ "label": "Enabled",
+ "value": "enabled"
+ },
+ {
+ "label": "Disabled",
+ "value": "disabled"
+ }
+ ]
+ }
+ ],
+ "label": "Set Authenticator Lite state",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.EnableFIDO2",
+ "helpText": "Enables the FIDO2 authenticationMethod for the tenant",
+ "addedComponent": [],
+ "label": "Enable FIDO2 capabilities",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.EnableHardwareOAuth",
+ "helpText": "Enables the HardwareOath authenticationMethod for the tenant. This allows you to use hardware tokens for generating 6 digit MFA codes.",
+ "addedComponent": [],
+ "label": "Enable Hardware OAuth tokens",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.allowOAuthTokens",
+ "helpText": "Allows you to use any software OAuth token generator",
+ "addedComponent": [],
+ "label": "Enable OTP Software OAuth tokens",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.TAP",
+ "helpText": "Enables TAP and sets the default TAP lifetime to 1 hour. This configuration also allows you to select is a TAP is single use or multi-logon.",
+ "addedComponent": [
+ {
+ "type": "Select",
+ "label": "Select TAP Lifetime",
+ "name": "standards.TAP.config",
+ "values": [
+ {
+ "label": "Only Once",
+ "value": "true"
+ },
+ {
+ "label": "Multiple Logons",
+ "value": "false"
+ }
+ ]
+ }
+ ],
+ "label": "Enable Temporary Access Passwords",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "cat": "Entra (AAD) Standards",
"name": "standards.PasswordExpireDisabled",
- "helpText": "Disables the expiration of passwords for the tenant",
- "addedComponent": null,
- "label": "Do not expire passwords"
- },
- {
- "cat": "AAD",
- "name": "standards.DisableSecurityGroupUsers",
- "helpText": "Completely disables the creation of security groups by users. This also breaks the ability to manage groups themselves, or create Teams",
- "addedComponent": null,
- "label": "Disable Security Group creation by users"
+ "helpText": "Disables the expiration of passwords for the tenant by setting the password expiration policy to never expire for any user.",
+ "addedComponent": [],
+ "label": "Do not expire passwords",
+ "impact": "Low Impact",
+ "impactColour": "info"
},
{
- "cat": "AAD",
+ "cat": "Entra (AAD) Standards",
"name": "standards.DisableTenantCreation",
- "helpText": "",
- "addedComponent": null,
- "label": "Disable M365 Tenant creation by users"
- },
- {
- "cat": "AAD",
- "name": "standards.OauthConsent.Enabled",
- "helpText": "",
- "addedComponent": {
- "type": "input",
- "name": "standards.OauthConsent.AllowedApps",
- "label": "Allowed application IDs, comma separated"
- },
- "label": "Require admin consent for applications (Prevent OAuth phishing.)"
- },
- {
- "cat": "AAD",
- "name": "standards.OauthConsentLowSec",
- "helpText": "",
- "label": "Allow users to consent to applications with low security risk (Prevent OAuth phishing. Lower impact, less secure.)"
- },
- {
- "cat": "AAD",
- "name": "standards.LegacyMFA",
- "helpText": "",
- "addedComponent": null,
- "label": "Enable per-user MFA for all user (Legacy, Requires DAP.)"
- },
- {
- "cat": "AAD",
- "name": "standards.LegacyMFACleanup",
- "helpText": "",
- "addedComponent": null,
- "label": "Remove Legacy MFA if SD or CA is active"
- },
- {
- "cat": "AAD",
+ "helpText": "Restricts creation of M365 tenants to the Global Administrator or Tenant Creator roles. ",
+ "addedComponent": [],
+ "label": "Disable M365 Tenant creation by users",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.EnableAppConsentRequests",
+ "helpText": "Enables App consent admin requests for the tenant via the GA role. Does not overwrite existing reviewer settings",
+ "addedComponent": [
+ {
+ "type": "AdminRolesMultiSelect",
+ "label": "App Consent Reviewer Roles",
+ "name": "standards.EnableAppConsentRequests.ReviewerRoles"
+ }
+ ],
+ "label": "Enable App consent admin requests",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "cat": "Entra (AAD) Standards",
"name": "standards.NudgeMFA.enable",
"helpText": "Enables registration campaign for the tenant",
- "addedComponent": null,
- "label": "Request to setup Authenticator if not setup yet."
+ "addedComponent": [],
+ "label": "Request to setup Authenticator if not setup yet",
+ "impact": "Low Impact",
+ "impactColour": "info"
},
{
- "cat": "AAD",
+ "cat": "Entra (AAD) Standards",
"name": "standards.NudgeMFA.disable",
"helpText": "Disables registration campaign for the tenant",
- "addedComponent": null,
- "label": "Disables the request to setup Authenticator if setup."
+ "addedComponent": [],
+ "label": "Disables the request to setup Authenticator if setup",
+ "impact": "Low Impact",
+ "impactColour": "info"
},
{
- "cat": "AAD",
- "name": "standards.DisableSelfServiceLicenses",
- "helpText": "",
- "addedComponent": null,
- "label": "Disable Self Service Licensing"
- },
- {
- "cat": "AAD",
+ "cat": "Entra (AAD) Standards",
"name": "standards.DisableM365GroupUsers",
- "helpText": "",
- "addedComponent": null,
- "label": "Disable M365 Group creation by users"
- },
- {
- "cat": "AAD",
- "name": "standards.UndoOauth",
- "helpText": "Disables App consent and set to Allow user consent for apps",
- "addedComponent": null,
- "label": "Undo App Consent Standard"
+ "helpText": "Restricts M365 group creation to certain admin roles. This disables the ability to create Teams, Sharepoint sites, Planner, etc",
+ "addedComponent": [],
+ "label": "Disable M365 Group creation by users",
+ "impact": "Low Impact",
+ "impactColour": "info"
},
{
- "cat": "AAD",
- "name": "standards.DisableGuests",
- "helpText": "",
- "addedComponent": null,
- "label": "Disable Guest accounts that have not logged on for 90 days"
- },
- {
- "cat": "AAD",
- "name": "standards.EnableFIDO2",
- "helpText": "Enables the FIDO2 authenticationMethod for the tenant",
- "addedComponent": null,
- "label": "Enable FIDO2 capabilities"
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.DisableSecurityGroupUsers",
+ "helpText": "Completely disables the creation of security groups by users. This also breaks the ability to manage groups themselves, or create Teams",
+ "addedComponent": [],
+ "label": "Disable Security Group creation by users",
+ "impact": "Medium Impact",
+ "impactColour": "warning"
},
{
- "name": "standards.OutBoundSpamAlert.Enabled",
- "cat": "Exchange",
- "helpText": "",
- "addedComponent": {
- "type": "input",
- "name": "standards.OutBoundSpamAlert.OutboundSpamContact",
- "label": "Outbound spam contact"
- },
- "label": "Set Outbound Spam Alert e-mail"
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.LegacyMFACleanup",
+ "helpText": "Removes legacy Per-User MFA if the tenant has Security Defaults or an All Users Conditional Access rule enabled.",
+ "addedComponent": [],
+ "label": "Remove Legacy MFA if SD or CA is active",
+ "impact": "Medium Impact",
+ "impactColour": "warning"
},
{
- "name": "standards.SafeSendersDisable",
- "cat": "Exchange",
- "helpText": "",
- "addedComponent": null,
- "label": "Remove Safe Senders to prevent SPF bypass"
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.DisableSelfServiceLicenses",
+ "helpText": "This standard currently does not function and can be safely disabled",
+ "addedComponent": [],
+ "label": "Disable Self Service Licensing",
+ "impact": "Medium Impact",
+ "impactColour": "warning"
},
{
- "name": "standards.DisableSharedMailbox",
- "cat": "Exchange",
- "helpText": "",
- "addedComponent": null,
- "label": "Disable Shared Mailbox AAD accounts"
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.DisableGuests",
+ "helpText": "Blocks login for guest users that have not logged in for 90 days",
+ "addedComponent": [],
+ "label": "Disable Guest accounts that have not logged on for 90 days",
+ "impact": "Medium Impact",
+ "impactColour": "warning"
+ },
+ {
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.OauthConsent",
+ "helpText": "Disables users from being able to consent to applications, except for those specified in the field below",
+ "addedComponent": [
+ {
+ "type": "input",
+ "name": "standards.OauthConsent.AllowedApps",
+ "label": "Allowed application IDs, comma separated"
+ }
+ ],
+ "label": "Require admin consent for applications (Prevent OAuth phishing)",
+ "impact": "Medium Impact",
+ "impactColour": "warning"
+ },
+ {
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.OauthConsentLowSec",
+ "helpText": "Sets the default oauth consent level so users can consent to applications that have low risks.",
+ "label": "Allow users to consent to applications with low security risk (Prevent OAuth phishing. Lower impact, less secure)",
+ "impact": "Medium Impact",
+ "impactColour": "warning"
},
{
- "name": "standards.DelegateSentItems",
- "cat": "Exchange",
- "helpText": "",
- "addedComponent": null,
- "label": "Set mailbox Sent Items delegation (Sent items for shared mailboxes)"
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.UndoOauth",
+ "disabledFeatures": {
+ "report": true,
+ "warn": true,
+ "remediate": false
+ },
+ "helpText": "Disables App consent and set to Allow user consent for apps",
+ "addedComponent": [],
+ "label": "Undo App Consent Standard",
+ "impact": "High Impact",
+ "impactColour": "danger"
},
{
- "name": "standards.SendFromAlias",
- "cat": "Exchange",
- "helpText": "",
- "addedComponent": null,
- "label": "Allow users to send from their alias addresses"
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.SecurityDefaults",
+ "helpText": "Enables security defaults for the tenant, for newer tenants this is enabled by default. Do not enable this feature if you use Conditional Access.",
+ "addedComponent": [],
+ "label": "Enable Security Defaults",
+ "impact": "High Impact",
+ "impactColour": "danger"
+ },
+ {
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.DisableSMS",
+ "helpText": "This blocks users from using SMS as an MFA method. If a user only has SMS as a MFA method, they will be unable to login.",
+ "addedComponent": [],
+ "label": "Disables SMS as an MFA method",
+ "impact": "High Impact",
+ "impactColour": "danger"
+ },
+ {
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.DisableVoice",
+ "helpText": "This blocks users from using Voice call as an MFA method. If a user only has Voice as a MFA method, they will be unable to login.",
+ "addedComponent": [],
+ "label": "Disables Voice call as an MFA method",
+ "impact": "High Impact",
+ "impactColour": "danger"
+ },
+ {
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.DisableEmail",
+ "helpText": "This blocks users from using email as an MFA method. This disables the email OTP option for guest users, and instead promts them to create a Microsoft account.",
+ "addedComponent": [],
+ "label": "Disables Email as an MFA method",
+ "impact": "High Impact",
+ "impactColour": "danger"
+ },
+ {
+ "cat": "Entra (AAD) Standards",
+ "name": "standards.Disablex509Certificate",
+ "helpText": "This blocks users from using Certificates as an MFA method.",
+ "addedComponent": [],
+ "label": "Disables Certificates as an MFA method",
+ "impact": "High Impact",
+ "impactColour": "danger"
+ },
+ {
+ "name": "standards.OutBoundSpamAlert",
+ "cat": "Exchange Standards",
+ "helpText": "Set the Outbound Spam Alert e-mail address",
+ "addedComponent": [
+ {
+ "type": "input",
+ "name": "standards.OutBoundSpamAlert.OutboundSpamContact",
+ "label": "Outbound spam contact"
+ }
+ ],
+ "label": "Set Outbound Spam Alert e-mail",
+ "impact": "Low Impact",
+ "impactColour": "info"
},
{
"name": "standards.AutoExpandArchive",
- "cat": "Exchange",
+ "cat": "Exchange Standards",
"helpText": "Enables auto-expanding archives for the tenant",
- "addedComponent": null,
- "label": "Enable Auto-expanding archives"
- },
- {
- "name": "standards.SpoofWarn.enable",
- "cat": "Exchange",
- "helpText": "Adds indicators to e-mail messages received from external senders in Outlook. Works on all Outlook clients/OWA",
- "addedComponent": null,
- "label": "Enable Spoofing warnings for Outlook (This e-mail is external identifiers)"
- },
- {
- "name": "standards.SpoofWarn.disable",
- "cat": "Exchange",
- "helpText": "",
- "addedComponent": null,
- "label": "Disable Spoofing warnings for Outlook (This e-mail is external identifiers)"
+ "addedComponent": [],
+ "label": "Enable Auto-expanding archives",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "name": "standards.SpoofWarn",
+ "cat": "Exchange Standards",
+ "helpText": "Adds or removes indicators to e-mail messages received from external senders in Outlook. Works on all Outlook clients/OWA",
+ "addedComponent": [
+ {
+ "type": "Select",
+ "label": "Select value",
+ "name": "standards.SpoofWarn.state",
+ "values": [
+ {
+ "label": "Enabled",
+ "value": "enabled"
+ },
+ {
+ "label": "Disabled",
+ "value": "disabled"
+ }
+ ]
+ }
+ ],
+ "label": "Enable or disable 'external' warning in Outlook",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "name": "standards.EnableMailTips",
+ "cat": "Exchange Standards",
+ "helpText": "Enables all MailTips in Outlook. MailTips are the notifications Outlook and Outlook on the web shows when an email you create, meets some requirements",
+ "addedComponent": [
+ {
+ "type": "number",
+ "name": "standards.EnableMailTips.MailTipsLargeAudienceThreshold",
+ "label": "Number of recipients to trigger the large audience MailTip (Default is 25)",
+ "placeholder": "Enter a profile name"
+ }
+ ],
+ "label": "Enable all MailTips",
+ "impact": "Low Impact",
+ "impactColour": "info"
},
{
"name": "standards.DisableViva",
- "cat": "Exchange",
- "helpText": "",
- "addedComponent": null,
- "label": "Disable daily Insight/Viva reports"
+ "cat": "Exchange Standards",
+ "helpText": "Disables the daily viva reports for all users.",
+ "addedComponent": [],
+ "label": "Disable daily Insight/Viva reports",
+ "impact": "Low Impact",
+ "impactColour": "info"
},
{
"name": "standards.RotateDKIM",
- "cat": "Exchange",
+ "cat": "Exchange Standards",
"helpText": "Rotate DKIM keys that are 1024 bit to 2048 bit",
- "addedComponent": null,
- "label": "Rotate DKIM keys that are 1024 bit to 2048 bit"
+ "addedComponent": [],
+ "label": "Rotate DKIM keys that are 1024 bit to 2048 bit",
+ "impact": "Low Impact",
+ "impactColour": "info"
},
{
"name": "standards.AddDKIM",
- "cat": "Exchange",
+ "cat": "Exchange Standards",
"helpText": "Enables DKIM for all domains that currently support it",
- "addedComponent": null,
- "label": "Enables DKIM for all domains that currently support it"
- },
- {
- "name": "standards.ActivityBasedTimeout",
- "cat": "Global",
- "helpText": "Enables and sets Idle session timeout for Microsoft 365 to 1 hour. This policy affects most M365 web apps",
- "addedComponent": null,
- "label": "Enable 1 hour Activity based Timeout"
- },
-
- {
- "name": "standards.calDefault.Enabled",
- "cat": "Exchange",
- "helpText": "",
- "addedComponent": {
- "type": "Select",
- "label": "Select Sharing Level",
- "name": "standards.calDefault.permissionlevel",
- "values": [
- {
- "label": "Owner - The user can create, read, edit, and delete all items in the folder, and create subfolders. The user is both folder owner and folder contact.",
- "value": "Owner"
- },
- {
- "label": "Publishing Editor - The user can create, read, edit, and delete all items in the folder, and create subfolders.",
- "value": "PublishingEditor"
- },
- {
- "label": "Reviewer - The user can read all items in the folder.",
- "value": "Reviewer"
- },
- {
- "label": "Editor - The user can create items in the folder. The contents of the folder do not appear.",
- "value": "Contributor"
- },
- {
- "label": "Limited Details - The user can view free/busy time within the calendar and the subject and location of appointments.",
- "value": "LimitedDetails"
- },
- {
- "label": "Availability Only - Indicates that the user can view only free/busy time within the calendar.",
- "value": "AvailabilityOnly"
- },
- {
- "label": "None - The user has no permissions on the folder.",
- "value": "none"
- }
- ]
- },
- "label": "Set Sharing Level for Default calendar"
- },
- {
- "name": "standards.UserSubmissions.enable",
- "cat": "Exchange",
- "helpText": "",
- "addedComponent": null,
- "label": "Enable the built-in Report button in Outlook"
- },
- {
- "name": "standards.UserSubmissions.disable",
- "cat": "Exchange",
- "helpText": "",
- "addedComponent": null,
- "label": "Disable the built-in Report button in Outlook"
- },
- {
- "name": "standards.intuneDeviceReg.Enabled",
- "cat": "Intune",
- "helpText": "",
- "addedComponent": {
- "type": "input",
- "name": "standards.intuneDeviceReg.max",
- "label": "Maximum devices (Enter 2147483647 for unlimited.)"
+ "addedComponent": [],
+ "label": "Enables DKIM for all domains that currently support it",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "name": "standards.EnableMailboxAuditing",
+ "cat": "Exchange Standards",
+ "helpText": "Enables Mailbox auditing for all mailboxes and on tenant level. By default Microsoft does not enable mailbox auditing for Resource Mailboxes, Public Folder Mailboxes and DiscoverySearch Mailboxes. Unified Audit Log needs to be enabled for this standard to function.",
+ "addedComponent": [],
+ "label": "Enable Mailbox auditing",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "name": "standards.SendReceiveLimitTenant",
+ "cat": "Exchange Standards",
+ "helpText": "Sets the Send and Receive limits for new users. Valid values are 1MB to 150MB",
+ "addedComponent": [
+ {
+ "type": "number",
+ "name": "standards.SendReceiveLimitTenant.SendLimit",
+ "label": "Send limit in MB (Default is 35)"
+ },
+ {
+ "type": "number",
+ "name": "standards.SendReceiveLimitTenant.ReceiveLimit",
+ "label": "Receive Limit in MB (Default is 36)"
+ }
+ ],
+ "label": "Set send/receive size limits",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "name": "standards.calDefault",
+ "cat": "Exchange Standards",
+ "helpText": "Sets the default sharing level for the default calendar, for all users",
+ "disabledFeatures": {
+ "report": true,
+ "warn": true,
+ "remediate": false
},
- "label": "Set Maximum Number of Devices per user"
+ "addedComponent": [
+ {
+ "type": "Select",
+ "label": "Select Sharing Level",
+ "name": "standards.calDefault.permissionlevel",
+ "values": [
+ {
+ "label": "Owner - The user can create, read, edit, and delete all items in the folder, and create subfolders. The user is both folder owner and folder contact.",
+ "value": "Owner"
+ },
+ {
+ "label": "Publishing Editor - The user can create, read, edit, and delete all items in the folder, and create subfolders.",
+ "value": "PublishingEditor"
+ },
+ {
+ "label": "Reviewer - The user can read all items in the folder.",
+ "value": "Reviewer"
+ },
+ {
+ "label": "Editor - The user can create items in the folder. The contents of the folder do not appear.",
+ "value": "Contributor"
+ },
+ {
+ "label": "Limited Details - The user can view free/busy time within the calendar and the subject and location of appointments.",
+ "value": "LimitedDetails"
+ },
+ {
+ "label": "Availability Only - Indicates that the user can view only free/busy time within the calendar.",
+ "value": "AvailabilityOnly"
+ },
+ {
+ "label": "None - The user has no permissions on the folder.",
+ "value": "none"
+ }
+ ]
+ }
+ ],
+ "label": "Set Sharing Level for Default calendar",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "name": "standards.DisableExternalCalendarSharing",
+ "cat": "Exchange Standards",
+ "helpText": "Disables the ability for users to share their calendar with external users. Only for the default policy, so exclusions can be made if needed.",
+ "addedComponent": [],
+ "label": "Disable external calendar sharing",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "name": "standards.DisableAdditionalStorageProviders",
+ "cat": "Exchange Standards",
+ "helpText": "Disables the ability for users to open files in Outlook on the Web, from other providers such as Box, Dropbox, Facebook, Google Drive, OneDrive Personal, etc.",
+ "addedComponent": [],
+ "label": "Disable additional storage providers in OWA",
+ "impact": "Low Impact",
+ "impactColour": "info"
},
{
- "name": "standards.intuneDeviceRetirementDays.Enabled",
- "cat": "Intune",
- "helpText": "A value between 0 and 270 is supported. A value of 0 disables retirement.",
- "addedComponent": {
- "type": "input",
- "name": "standards.intuneDeviceRetirementDays.days",
- "label": "Maximum days (0 equals disabled)"
+ "name": "standards.SafeSendersDisable",
+ "cat": "Exchange Standards",
+ "disabledFeatures": {
+ "report": true,
+ "warn": true,
+ "remediate": false
},
- "label": "Set inactive device retirement days"
+ "helpText": "Loops through all users and removes the Safe Senders list. This is to prevent SPF bypass attacks, as the Safe Senders list is not checked by SPF.",
+ "addedComponent": [],
+ "label": "Remove Safe Senders to prevent SPF bypass",
+ "impact": "Medium Impact",
+ "impactColour": "warning"
},
{
- "name": "standards.intuneRequireMFA",
- "cat": "Intune",
- "helpText": "",
- "label": "Require Multifactor Authentication to register or join devices with Microsoft Entra"
- },
- {
- "name": "standards.sharingCapability.Enabled",
- "cat": "SharePoint",
- "helpText": "",
- "addedComponent": {
- "type": "Select",
- "label": "Select Sharing Level",
- "name": "standards.sharingCapability.Level",
- "values": [
- {
- "label": "Users can share only with people in the organization. No external sharing is allowed.",
- "value": "disabled"
- },
- {
- "label": "Users can share with new and existing guests. Guests must sign in or provide a verification code.",
- "value": "externalUserSharingOnly"
- },
- {
- "label": "Users can share with anyone by using links that do not require sign-in.",
- "value": "externalUserAndGuestSharing"
- },
- {
- "label": "Users can share with existing guests (those already in the directory of the organization).",
- "value": "existingExternalUserSharingOnly"
- }
- ]
- },
- "label": "Set Sharing Level for OneDrive and Sharepoint"
+ "name": "standards.DelegateSentItems",
+ "cat": "Exchange Standards",
+ "helpText": "Sets emails sent as and on behalf of shared mailboxes to also be stored in the shared mailbox sent items folder",
+ "addedComponent": [],
+ "label": "Set mailbox Sent Items delegation (Sent items for shared mailboxes)",
+ "impact": "Medium Impact",
+ "impactColour": "warning"
},
{
- "name": "standards.ExcludedfileExt.Enabled",
- "cat": "SharePoint",
- "helpText": "",
- "addedComponent": {
- "type": "input",
- "name": "standards.ExcludedfileExt.ext",
- "label": "Extensions, Comma separated"
- },
- "label": "Exclude File Extensions from Syncing"
+ "name": "standards.SendFromAlias",
+ "cat": "Exchange Standards",
+ "helpText": "Enables the ability for users to send from their alias addresses.",
+ "addedComponent": [],
+ "label": "Allow users to send from their alias addresses",
+ "impact": "Medium Impact",
+ "impactColour": "warning"
},
{
- "name": "standards.disableMacSync",
- "cat": "SharePoint",
- "helpText": "",
- "addedComponent": null,
- "label": "Do not allow Mac devices to sync using OneDrive"
+ "name": "standards.UserSubmissions.enable",
+ "cat": "Exchange Standards",
+ "helpText": "Enables the spam submission button in Outlook",
+ "addedComponent": [],
+ "label": "Enable the built-in Report button in Outlook",
+ "impact": "Medium Impact",
+ "impactColour": "warning"
},
{
- "name": "standards.DisableReshare",
- "cat": "SharePoint",
- "helpText": "",
- "addedComponent": null,
- "label": "Disable Resharing by External Users"
+ "name": "standards.UserSubmissions.disable",
+ "cat": "Exchange Standards",
+ "helpText": "Disables the spam submission button in Outlook",
+ "addedComponent": [],
+ "label": "Disable the built-in Report button in Outlook",
+ "impact": "Medium Impact",
+ "impactColour": "warning"
},
{
- "name": "standards.DeletedUserRentention",
- "cat": "SharePoint",
- "helpText": "Sets the retenton period for deleted users OneDrive to 1 year/365 days",
- "addedComponent": null,
- "label": "Retain a deleted user OneDrive for 1 year"
+ "name": "standards.DisableSharedMailbox",
+ "cat": "Exchange Standards",
+ "helpText": "Blocks login for all accounts that are marked as a shared mailbox. This is Microsoft best practice to prevent direct logons to shared mailboxes.",
+ "addedComponent": [],
+ "label": "Disable Shared Mailbox AAD accounts",
+ "impact": "Medium Impact",
+ "impactColour": "warning"
+ },
+ {
+ "name": "standards.intuneDeviceRetirementDays",
+ "cat": "Intune Standards",
+ "helpText": "A value between 0 and 270 is supported. A value of 0 disables retirement, retired devices are removed from Intune after the specified number of days.",
+ "addedComponent": [
+ {
+ "type": "input",
+ "name": "standards.intuneDeviceRetirementDays.days",
+ "label": "Maximum days (0 equals disabled)"
+ }
+ ],
+ "label": "Set inactive device retirement days",
+ "impact": "Low Impact",
+ "impactColour": "info"
+ },
+ {
+ "name": "standards.intuneDeviceReg",
+ "cat": "Intune Standards",
+ "helpText": "sets the maximum number of devices that can be registered by a user. A value of 0 disables device registration by users",
+ "addedComponent": [
+ {
+ "type": "input",
+ "name": "standards.intuneDeviceReg.max",
+ "label": "Maximum devices (Enter 2147483647 for unlimited.)"
+ }
+ ],
+ "label": "Set Maximum Number of Devices per user",
+ "impact": "Medium Impact",
+ "impactColour": "warning"
},
{
- "name": "standards.DisableUserSiteCreate",
- "cat": "SharePoint",
- "helpText": "Disables users from creating new SharePoint sites",
- "addedComponent": null,
- "label": "Disable site creation by standard users"
+ "name": "standards.intuneRequireMFA",
+ "cat": "Intune Standards",
+ "helpText": "Requires MFA for all users to register devices with Intune. This is useful when not using Conditional Access.",
+ "label": "Require Multifactor Authentication to register or join devices with Microsoft Entra",
+ "impact": "Medium Impact",
+ "impactColour": "warning"
},
{
- "name": "standards.unmanagedSync",
- "cat": "SharePoint",
- "helpText": "",
- "addedComponent": null,
- "label": "Only allow users to sync OneDrive from AAD joined devices"
+ "name": "standards.DeletedUserRentention",
+ "cat": "SharePoint Standards",
+ "helpText": "Sets the retention period for deleted users OneDrive to 1 year/365 days",
+ "addedComponent": [],
+ "label": "Retain a deleted user OneDrive for 1 year",
+ "impact": "Low Impact",
+ "impactColour": "info"
},
{
"name": "standards.DisableAddShortcutsToOneDrive",
- "cat": "SharePoint",
- "helpText": "When the feature is disabled the option Add shortcut to My files will be removed; any folders that have already been added will remain on the user's computer.",
- "addedComponent": null,
- "label": "Disable Add Shortcuts To OneDrive"
- },
- {
- "name": "standards.IntuneTemplate.enabled",
- "cat": "templates",
- "helpText": "",
- "addedComponent": null,
- "label": "Intune Templates"
+ "cat": "SharePoint Standards",
+ "helpText": "When the feature is disabled the option Add shortcut to OneDrive will be removed. Any folders that have already been added will remain on the user's computer.",
+ "disabledFeatures": {
+ "report": true,
+ "warn": true,
+ "remediate": false
+ },
+ "addedComponent": [],
+ "label": "Disable Add Shortcuts To OneDrive",
+ "impact": "Medium Impact",
+ "impactColour": "warning"
+ },
+ {
+ "name": "standards.sharingCapability",
+ "cat": "SharePoint Standards",
+ "helpText": "Sets the default sharing level for OneDrive and Sharepoint. This is a tenant wide setting and overrules any settings set on the site level",
+ "addedComponent": [
+ {
+ "type": "Select",
+ "label": "Select Sharing Level",
+ "name": "standards.sharingCapability.Level",
+ "values": [
+ {
+ "label": "Users can share only with people in the organization. No external sharing is allowed.",
+ "value": "disabled"
+ },
+ {
+ "label": "Users can share with new and existing guests. Guests must sign in or provide a verification code.",
+ "value": "externalUserSharingOnly"
+ },
+ {
+ "label": "Users can share with anyone by using links that do not require sign-in.",
+ "value": "externalUserAndGuestSharing"
+ },
+ {
+ "label": "Users can share with existing guests (those already in the directory of the organization).",
+ "value": "existingExternalUserSharingOnly"
+ }
+ ]
+ }
+ ],
+ "label": "Set Sharing Level for OneDrive and Sharepoint",
+ "impact": "High Impact",
+ "impactColour": "danger"
},
{
- "name": "standards.GroupTemplate.enabled",
- "cat": "templates",
- "helpText": "",
- "addedComponent": null,
- "label": "Group Templates"
+ "name": "standards.DisableReshare",
+ "cat": "SharePoint Standards",
+ "helpText": "Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access",
+ "addedComponent": [],
+ "label": "Disable Resharing by External Users",
+ "impact": "High Impact",
+ "impactColour": "danger"
},
{
- "name": "standards.ExConnector.enabled",
- "cat": "templates",
- "helpText": "",
- "addedComponent": null,
- "label": "Exchange Connector Templates"
+ "name": "standards.DisableUserSiteCreate",
+ "cat": "SharePoint Standards",
+ "helpText": "Disables users from creating new SharePoint sites",
+ "addedComponent": [],
+ "label": "Disable site creation by standard users",
+ "impact": "High Impact",
+ "impactColour": "danger"
+ },
+ {
+ "name": "standards.ExcludedfileExt",
+ "cat": "SharePoint Standards",
+ "helpText": "Sets the file extensions that are excluded from syncing with OneDrive. These files will be blocked from upload.",
+ "addedComponent": [
+ {
+ "type": "input",
+ "name": "standards.ExcludedfileExt.ext",
+ "label": "Extensions, Comma separated"
+ }
+ ],
+ "label": "Exclude File Extensions from Syncing",
+ "impact": "High Impact",
+ "impactColour": "danger"
},
{
- "name": "standards.ConditionalAccess.enabled",
- "cat": "templates",
- "helpText": "",
- "addedComponent": null,
- "label": "Conditional Access Templates"
+ "name": "standards.disableMacSync",
+ "cat": "SharePoint Standards",
+ "helpText": "Disables the ability for Mac devices to sync with OneDrive.",
+ "addedComponent": [],
+ "label": "Do not allow Mac devices to sync using OneDrive",
+ "impact": "High Impact",
+ "impactColour": "danger"
},
{
- "name": "standards.TransportRuleTemplate.enabled",
- "cat": "templates",
- "helpText": "",
- "addedComponent": null,
- "label": "Transport Rule Templates"
+ "name": "standards.unmanagedSync",
+ "cat": "SharePoint Standards",
+ "helpText": "This standard will only allow devices that are AD joined, or AAD joined to sync with OneDrive",
+ "addedComponent": [],
+ "label": "Only allow users to sync OneDrive from AAD joined devices",
+ "impact": "High Impact",
+ "impactColour": "danger"
}
]
diff --git a/src/data/vendorTenantList.json b/src/data/vendorTenantList.json
new file mode 100644
index 000000000000..de12e3508819
--- /dev/null
+++ b/src/data/vendorTenantList.json
@@ -0,0 +1,270 @@
+[
+ {
+ "vendorName": "Augmentt",
+ "vendorTenantId": "29f0611d-e8d3-4946-914c-677d2ad8065b"
+ },
+ {
+ "vendorName": "Rewst",
+ "vendorTenantId": "5a9dda56-5beb-4840-83e1-9afc1ada2388"
+ },
+ {
+ "vendorName": "Datto Backupify",
+ "vendorTenantId": "8ebde5a4-a587-497c-9881-8a5c272dd1c4"
+ },
+ {
+ "vendorName": "Eshgro Smarter365",
+ "vendorTenantId": "0a3132f8-cbcd-430f-a554-b0490bea8018"
+ },
+ {
+ "vendorName": "BitTitan",
+ "vendorTenantId": "6690473e-14f0-4f77-bf88-2ae5ade8746c"
+ },
+ {
+ "vendorName": "Ingram Micro CPV",
+ "vendorTenantId": "5abe7315-18a5-4ca1-8e51-99753bef0d96"
+ },
+ {
+ "vendorName": "CodeTwo",
+ "vendorTenantId": "d4fd5c8b-42b0-4e2d-b330-498809917d07"
+ },
+ {
+ "vendorName": "Anywhere365",
+ "vendorTenantId": "4179dd4d-e485-4535-9d45-b3539f584cad"
+ },
+ {
+ "vendorName": "Roger365",
+ "vendorTenantId": "3c59ec71-67c8-4f3d-b2bd-19152b2629e1"
+ },
+ {
+ "vendorName": "ESET",
+ "vendorTenantId": "01f7e0e8-c680-4293-8068-d572231a88f4"
+ },
+ {
+ "vendorName": "CyberTwice",
+ "vendorTenantId": "0c1fb16c-0190-47e0-b839-5ec8665eb699"
+ },
+ {
+ "vendorName": "CloudMore AB",
+ "vendorTenantId": "0cc4f6a9-d96a-4508-b938-32386e1c44cf"
+ },
+ {
+ "vendorName": "Google Workspace",
+ "vendorTenantId": "0f8cb250-b44f-4acd-b24e-2524ef9f85ac"
+ },
+ {
+ "vendorName": "Merill",
+ "vendorTenantId": "10407d69-1ba5-4bec-8ebe-9af2f0b9e06a"
+ },
+ {
+ "vendorName": "Citrix Cloud",
+ "vendorTenantId": "13d925d3-c1fe-4447-9600-bb8572753a33"
+ },
+ {
+ "vendorName": "Proofpoint",
+ "vendorTenantId": "1d308ae7-d771-4a4a-8857-409aee86644a"
+ },
+ {
+ "vendorName": "Datto RMM",
+ "vendorTenantId": "353488c3-d84b-48e4-95ba-2456645d67bf"
+ },
+ {
+ "vendorName": "Avepoint",
+ "vendorTenantId": "3ee8362e-4c3c-447b-9a9e-910eec5d89c8"
+ },
+ {
+ "vendorName": "SkyKick",
+ "vendorTenantId": "3f9cd7a0-127a-4ed4-bf06-2f830d5616b7"
+ },
+ {
+ "vendorName": "Carbonite",
+ "vendorTenantId": "4f0566c5-7c80-40a4-b3e8-7cfcb6c423ff"
+ },
+ {
+ "vendorName": "Hornet Security/Altaro",
+ "vendorTenantId": "58fba382-61c2-47f8-92b1-296e14787b85"
+ },
+ {
+ "vendorName": "N-able",
+ "vendorTenantId": "6324f4fb-86ee-4493-ba16-c819a916b487"
+ },
+ {
+ "vendorName": "Freshdesk",
+ "vendorTenantId": "6cdfee3a-1922-41c3-8e74-f4ae694fae8a"
+ },
+ {
+ "vendorName": "3CX",
+ "vendorTenantId": "6e2eadce-56c5-4116-a790-14d391b48eac"
+ },
+ {
+ "vendorName": "Pckgr",
+ "vendorTenantId": "6fd3fafe-81c9-41da-b84b-143ba2908cbb"
+ },
+ {
+ "vendorName": "AdminDroid",
+ "vendorTenantId": "7228f07f-efee-4159-9393-be6efb253fd0"
+ },
+ {
+ "vendorName": "Exclaimer",
+ "vendorTenantId": "74bebcc8-1c15-4717-b538-bf84dea9e50f"
+ },
+ {
+ "vendorName": "Bastion365",
+ "vendorTenantId": "7bfd6512-414b-4fb1-9529-3ac3f9e7ec26"
+ },
+ {
+ "vendorName": "Ricoh",
+ "vendorTenantId": "7e7a67d3-42f4-4a40-b8f0-f998ca7018ab"
+ },
+ {
+ "vendorName": "Hubspot",
+ "vendorTenantId": "8c6c4f2c-3b97-427e-ab0f-1b982441831a"
+ },
+ {
+ "vendorName": "TrendMicro",
+ "vendorTenantId": "8d3dd3ec-a7ee-4c65-8cea-01e3f1492f1b"
+ },
+ {
+ "vendorName": "Synology",
+ "vendorTenantId": "9ba572a0-0623-4ab6-96ad-74cf9f3631fe"
+ },
+ {
+ "vendorName": "Scappman",
+ "vendorTenantId": "b2de4365-2ff4-4021-ac3b-fd1d2abb8de7"
+ },
+ {
+ "vendorName": "Barracuda Networks",
+ "vendorTenantId": "b893e0b8-2743-4fa7-81eb-0155a9060350"
+ },
+ {
+ "vendorName": "Zivver",
+ "vendorTenantId": "c3ea126d-08dd-4b6a-a23a-4a513160c11e"
+ },
+ {
+ "vendorName": "HP",
+ "vendorTenantId": "ca7981a2-785a-463d-b82a-3db87dfc3ce6"
+ },
+ {
+ "vendorName": "ManageEngine",
+ "vendorTenantId": "dab88ba4-c480-4a0f-a42b-6d66fba460ff"
+ },
+ {
+ "vendorName": "Action1",
+ "vendorTenantId": "e1a62259-76ab-47a0-9e9d-b91f3b08cbd5"
+ },
+ {
+ "vendorName": "ShareGate",
+ "vendorTenantId": "eb39acb7-fae3-4bc3-974c-b765aa1d6355"
+ },
+ {
+ "vendorName": "Liquit",
+ "vendorTenantId": "2ea2a5a1-1e01-4ecc-ba61-12fb51c1c12d"
+ },
+ {
+ "vendorName": "Phished",
+ "vendorTenantId": "9f7b0cc1-2df8-442e-b902-84371e12af00"
+ },
+ {
+ "vendorName": "Octiga Security",
+ "vendorTenantId": "b7453564-2f58-4ad6-b7ba-c6e35e8de6bb"
+ },
+ {
+ "vendorName": "Printix",
+ "vendorTenantId": "2e5d89d1-0b98-45d8-b70f-c79db10feb54"
+ },
+ {
+ "vendorName": "Usecure",
+ "vendorTenantId": "7d88f9a5-1574-4b92-82e4-e05712338cf4"
+ },
+ {
+ "vendorName": "Redstor",
+ "vendorTenantId": "24ac53ae-15a7-4211-afef-61d8f34e2571"
+ },
+ {
+ "vendorName": "Axcient",
+ "vendorTenantId": "adc01291-a3ea-4fb8-8fa7-d8f6dd816182"
+ },
+ {
+ "vendorName": "NinjaOne",
+ "vendorTenantId": "0e0adb39-f83f-4576-9102-db1b902ca108"
+ },
+ {
+ "vendorName": "SuperVision (KPN)",
+ "vendorTenantId": "8edc1ef5-a81d-4229-badb-e2634a284461"
+ },
+ {
+ "vendorName": "MSPMagic",
+ "vendorTenantId": "74d3d0de-bbd7-433f-95c2-40cc5d185968"
+ },
+ {
+ "vendorName": "SimeonCloud",
+ "vendorTenantId": "3d945cb7-f7da-444c-8c9e-93c3226581ec"
+ },
+ {
+ "vendorName": "KeepIt",
+ "vendorTenantId": "55c67891-c2e6-4278-a07e-5391e269777e"
+ },
+ {
+ "vendorName": "Arrow Sphere",
+ "vendorTenantId": "0beb0c35-9cbb-4feb-99e5-589e415c7944"
+ },
+ {
+ "vendorName": "Auvik",
+ "vendorTenantId": "408f0e54-582e-46fa-8b7d-4068bb176500"
+ },
+ {
+ "vendorName": "Avanan",
+ "vendorTenantId": "5c37d5b5-54f7-46e7-bc09-2eaa1ef1b541"
+ },
+ {
+ "vendorName": "CloudRadial",
+ "vendorTenantId": "845fbb26-e9d0-465d-8d4f-40f490928179"
+ },
+ {
+ "vendorName": "Duo",
+ "vendorTenantId": "3c453cf7-43fe-4e45-a9b9-f168f480f0f8"
+ },
+ {
+ "vendorName": "Metallic",
+ "vendorTenantId": "da72dd62-58c6-4062-abf9-47be4e73c0f6"
+ },
+ {
+ "vendorName": "Sophos",
+ "vendorTenantId": "358a41ff-46d9-49d3-a297-370d894eae6a"
+ },
+ {
+ "vendorName": "SaaSAlerts",
+ "vendorTenantId": "5c7b2b48-9e8f-49ba-80d6-3432e39d596b"
+ },
+ {
+ "vendorName": "BullPhish (Ciranda)",
+ "vendorTenantId": "5e4ab895-7a4c-4eea-bb39-75edca0421ad"
+ },
+ {
+ "vendorName": "Coreview",
+ "vendorTenantId": "73506dd6-2bc3-49c0-92f3-b2877bab00ba"
+ },
+ {
+ "vendorName": "Quickpass (CyberQP365)",
+ "vendorTenantId": "c9006408-eb26-4e50-9bd5-2c078e3dc844"
+ },
+ {
+ "vendorName": "Infima",
+ "vendorTenantId": "2bd37396-af18-448c-a391-dd7800364e6f"
+ },
+ {
+ "vendorName": "Immybot",
+ "vendorTenantId": "1dcfdedd-ec87-461d-9d55-6989a519d154"
+ },
+ {
+ "vendorName": "N-Able/Cove backup",
+ "vendorTenantId": "521c973d-080f-4861-a0cb-8939e59d3d39"
+ },
+ {
+ "vendorName": "Dropsuite",
+ "vendorTenantId": "5b8e57d8-5c8e-4b82-98a5-b003bbb26b31"
+ },
+ {
+ "vendorName": "Huntress",
+ "vendorTenantId": "19be9add-482a-4c98-ba76-4c2ef7f3bb13"
+ }
+]
diff --git a/src/hooks/useConfirmModal.js b/src/hooks/useConfirmModal.jsx
similarity index 100%
rename from src/hooks/useConfirmModal.js
rename to src/hooks/useConfirmModal.jsx
diff --git a/src/hooks/useQuery.js b/src/hooks/useQuery.jsx
similarity index 100%
rename from src/hooks/useQuery.js
rename to src/hooks/useQuery.jsx
diff --git a/src/index.js b/src/index.js
deleted file mode 100644
index 975affa81577..000000000000
--- a/src/index.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import 'react-app-polyfill/stable'
-import 'core-js'
-import React from 'react'
-import ReactDOM from 'react-dom'
-import App from 'src/App'
-import * as serviceWorker from 'src/serviceWorker'
-import { Provider } from 'react-redux'
-import { store, persistor } from 'src/store'
-import { PersistGate } from 'redux-persist/integration/react'
-import { FullScreenLoading } from 'src/components/utilities'
-
-ReactDOM.render(
-
-
- } persistor={persistor}>
-
-
-
- ,
- document.getElementById('root'),
-)
-
-// If you want your app to work offline and load faster, you can change
-// unregister() to register() below. Note this comes with some pitfalls.
-// Learn more about service workers: http://bit.ly/CRA-PWA
-serviceWorker.unregister()
diff --git a/src/index.jsx b/src/index.jsx
new file mode 100644
index 000000000000..81aee3c89edb
--- /dev/null
+++ b/src/index.jsx
@@ -0,0 +1,27 @@
+import 'react-app-polyfill/stable'
+import 'core-js'
+import React from 'react'
+import { createRoot } from 'react-dom/client'
+import App from 'src/App'
+import { Provider } from 'react-redux'
+import { store, persistor } from 'src/store'
+import { PersistGate } from 'redux-persist/integration/react'
+import { FullScreenLoading } from 'src/components/utilities'
+import { HelmetProvider } from 'react-helmet-async'
+
+const container = document.getElementById('root')
+
+const root = createRoot(container)
+
+root.render(
+ // @TODO fix issues preventing app from running with StrictMode enabled
+ //
+
+ } persistor={persistor}>
+
+
+
+
+ ,
+ // ,
+)
diff --git a/src/layout/DefaultLayout.js b/src/layout/DefaultLayout.jsx
similarity index 98%
rename from src/layout/DefaultLayout.js
rename to src/layout/DefaultLayout.jsx
index f5721117185f..07ec1d0ed228 100644
--- a/src/layout/DefaultLayout.js
+++ b/src/layout/DefaultLayout.jsx
@@ -51,9 +51,10 @@ const DefaultLayout = () => {
-
+
+
}>
diff --git a/src/routes.js b/src/routes.js
index 5be9247bfae0..2f41ef51dff5 100644
--- a/src/routes.js
+++ b/src/routes.js
@@ -23,6 +23,7 @@ const DeployGroupTemplates = React.lazy(() =>
const GeoIPLookup = React.lazy(() => import('src/views/tenant/administration/GeoIPLookup'))
const TenantLookup = React.lazy(() => import('src/views/tenant/administration/TenantLookup'))
+
const GroupTemplates = React.lazy(() => import('src/views/identity/administration/GroupTemplates'))
const EditGroup = React.lazy(() => import('src/views/identity/administration/EditGroup'))
@@ -38,12 +39,15 @@ const Page500 = React.lazy(() => import('src/views/pages/page500/Page500'))
const MFAReport = React.lazy(() => import('src/views/identity/reports/MFAReport'))
const Tenants = React.lazy(() => import('src/views/tenant/administration/Tenants'))
const AlertWizard = React.lazy(() => import('src/views/tenant/administration/AlertWizard'))
+const AlertRules = React.lazy(() => import('src/views/tenant/administration/AlertRules'))
+
const AlertsQueue = React.lazy(() => import('src/views/tenant/administration/ListAlertsQueue'))
const GraphExplorer = React.lazy(() => import('src/views/tenant/administration/GraphExplorer'))
const Domains = React.lazy(() => import('src/views/tenant/administration/Domains'))
const EditTenant = React.lazy(() => import('src/views/tenant/administration/EditTenant'))
const ConditionalAccess = React.lazy(() => import('src/views/tenant/conditional/ConditionalAccess'))
+const DeployVacationCA = React.lazy(() => import('src/views/tenant/conditional/DeployVacation'))
const NamedLocations = React.lazy(() => import('src/views/tenant/conditional/NamedLocations'))
const ListConditionalTemplates = React.lazy(() =>
@@ -225,6 +229,9 @@ const ServiceHealth = React.lazy(() => import('src/views/tenant/administration/S
const EnterpriseApplications = React.lazy(() =>
import('src/views/tenant/administration/ListEnterpriseApps'),
)
+const AppConsentRequests = React.lazy(() =>
+ import('src/views/tenant/administration/ListAppConsentRequests'),
+)
const MailboxRestoreWizard = React.lazy(() =>
import('src/views/email-exchange/tools/MailboxRestoreWizard'),
)
@@ -307,6 +314,8 @@ const routes = [
{ path: '/tenant/administration/tenants/edit', name: 'Edit Tenant', component: EditTenant },
{ path: '/tenant/administration/domains', name: 'Domains', component: Domains },
{ path: '/tenant/administration/alertswizard', name: 'Alerts Wizard', component: AlertWizard },
+ { path: '/tenant/administration/alertrules', name: 'Alerts Wizard', component: AlertRules },
+
{ path: '/tenant/administration/alertsqueue', name: 'Alerts Queue', component: AlertsQueue },
{
path: '/tenant/administration/graph-explorer',
@@ -323,11 +332,21 @@ const routes = [
name: 'Enterprise Applications',
component: EnterpriseApplications,
},
+ {
+ path: '/tenant/administration/app-consent-requests',
+ name: 'App Consent Requests',
+ component: AppConsentRequests,
+ },
{
path: '/tenant/conditional/list-policies',
name: 'Conditional Access',
component: ConditionalAccess,
},
+ {
+ path: '/tenant/conditional/deploy-vacation',
+ name: 'Deploy Vacation Mode',
+ component: DeployVacationCA,
+ },
{
path: '/tenant/conditional/list-named-locations',
name: 'Named Locations',
diff --git a/src/scss/_custom.scss b/src/scss/_custom.scss
index 8fccbc31607b..60d5abcc0cdd 100644
--- a/src/scss/_custom.scss
+++ b/src/scss/_custom.scss
@@ -3,6 +3,9 @@
position: relative;
display: inline-block;
}
+body {
+ overflow-y: scroll; // Always show vertical scrollbar
+}
.image-upload-container {
position: relative;
width: 100px; /* Adjusted to match the image */
@@ -298,10 +301,6 @@ h3.underline:after {
}
}
-.cipp-offcanvastable {
- font-size: smaller;
-}
-
.dropdown-item {
a {
color: var(--cui-dropdown-link-color);
@@ -463,11 +462,12 @@ h3.underline:after {
}
/* Footer */
+
.footer {
p {
margin-bottom: 0;
}
-
+ background: var(--cui-body-bg);
img {
max-height: 2rem;
}
@@ -546,11 +546,9 @@ h3.underline:after {
}
.wrapper.d-flex.flex-column.min-vh-100 {
+ //full width -20px for scrollbar bouncing
background-color: var(--cui-color-gray-1) !important;
- padding-top: 30px;
- min-height: calc(100vh - 84px) !important;
z-index: 2;
-
// .body.flex-grow-1.px-xl-3>.container-fluid>div>div{
// background: var(--cui-color-white) !important;
// padding: 20px 30px;
@@ -644,16 +642,10 @@ h3.underline:after {
.sidebar-brand {
background: none;
.sidebar-brand-full {
- margin-left: 30px;
- height: 50px;
+ margin: 15px;
transform: scale(1.5);
}
}
-
-.sidebar.sidebar-fixed {
- margin-top: 69px;
- height: calc(100vh - 69px);
-}
.wrapper.d-flex.flex-column.min-vh-100 .card {
margin-bottom: 0px;
}
diff --git a/src/scss/_tenantselector.scss b/src/scss/_tenantselector.scss
index 3ae83ce3746e..ec0c93342406 100644
--- a/src/scss/_tenantselector.scss
+++ b/src/scss/_tenantselector.scss
@@ -41,9 +41,9 @@
&__input {
appearance: none;
background: var(--cipp-search-bg);
- border: 1px solid var(--cipp-search-border-color);
border-radius: var(--cipp-border-radius);
box-shadow: 0 0.0625rem 0.125rem rgb(0 0 0 / 15%);
+ border: 1px solid var(--cipp-search-border-color);
color: var(--cipp-search-color);
display: block;
font-family: 'Noto Sans', sans-serif;
@@ -114,8 +114,7 @@
&__select {
background: var(--cipp-search-bg);
- border: 1px solid var(--cipp-search-border-color);
- box-shadow: 0.25rem 0.25rem 0.125rem rgb(0 0 0 / 50%);
+ border-radius: var(--cipp-border-radius);
color: var(--cipp-search-color);
}
@@ -206,4 +205,15 @@
.tenantDropdown {
min-width: 25rem;
+ max-width: 30%;
+
+ .select-search {
+ &__input {
+ border: none;
+
+ &:hover {
+ border: none;
+ }
+ }
+ }
}
diff --git a/src/scss/_themes.scss b/src/scss/_themes.scss
index ccbfa5a592c3..b427f810d76a 100644
--- a/src/scss/_themes.scss
+++ b/src/scss/_themes.scss
@@ -10,7 +10,7 @@
--cyberdrain-light-rgb: rgb(244 245 246); // Cultured
--cyberdrain-light-striped: #d2d6da; // Light Gray
--cyberdrain-darker: #20262a; // Charleston Green
- --cyberdrain-dark: #2e363a; // Gunmetal
+ --cyberdrain-dark: #121212; // Gunmetal
--cyberdrain-dark-rgb: rgb(46 54 58); // Gunmetal
--cyberdrain-dark-striped: #48555b; // Charcoal
--cyberdrain-accent-blue: #3e5c66; // Deep Space Sparkle
@@ -264,9 +264,9 @@
--cui-input-focus-bg: var(--cyberdrain-light-striped);
--cui-input-focus-border-colour: var(--cyberdrain-primary);
--cui-input-focus-color: var(--cyberdrain-dark);
- --cui-link-color: var(--cyberdrain-dark);
+ --cui-link-color-rgb: var(--cyberdrain-dark);
--cui-link-hover-color: var(--cyberdrain-primary);
- --cui-list-group-bg: var(--cui-color-white);
+ --cui-list-group-bg: var(--cui-color-white) !important;
--cui-list-group-color: var(--cyberdrain-dark);
--cui-modal-content-bg: var(--cyberdrain-light);
--cui-modal-content-border-color: var(--cyberdrain-accent-green);
@@ -285,15 +285,17 @@
--cui-nav-link-hover-color: var(--cyberdrain-dark);
--cui-nav-tabs-border-color: var(--cyberdrain-accent-green);
--cui-nav-tabs-link-hover-border-color: var(--cyberdrain-accent-green);
- --cui-toast-background-color: var(--cui-color-white);
--cui-toast-border-color: var(--cyberdrain-accent-green);
--cui-toast-color: var(--cui-color-black);
- --cui-toast-header-background-color: var(--cui-color-white);
--cui-toast-header-color: var(--cui-color-black);
--cui-card-cap-color: var(--cyberdrain-dark);
--cui-card-cap-bg: var(--cui-color-white);
// CIPP CyberDrain theme variables.
+ --cipp-toast-header-bg: var(--cui-color-white);
+ --cipp-toast-bg: var(--cui-color-white);
+ --cipp-toast-color: var(--cui-color-black);
+
--cipp-fa-inverse-color: var(--cyberdrain-dark);
--cipp-search-bg: var(--cyberdrain-light);
--cipp-search-border-color: var(--cyberdrain-accent-green);
@@ -310,7 +312,7 @@
--cipp-table-primary-colour: var(--cyberdrain-dark);
--cipp-table-secondary-colour: var(--cyberdrain-secondary);
--cipp-table-sort-focus-bg: var(--cyberdrain-secondary);
- --cipp-table-highlight-on-hover-bg: rgb(150, 150, 150);
+ //--cipp-table-highlight-on-hover-bg: rgb(150, 150, 150);
--cipp-table-highlight-on-hover-color: rgb(150, 150, 150);
--cipp-table-striped-bg: var(--cyberdrain-light-striped);
--cipp-table-striped-colour: var(--cyberdrain-dark-striped);
@@ -398,7 +400,7 @@
[data-theme='impact'] {
// Custom Ian Color
- --cui-color-gray: rgb(46, 54, 58);
+ --cui-color-gray: #1e1e1e;
// --cui-color-gray1: rgb(15,15,15);
// --cui-color-gray2: rgb(10, 10, 10);
--cui-color-header-bar: rgba(40, 40, 40, 0.8);
@@ -407,8 +409,14 @@
--cui-bgcolor-table-header: rgba(105, 105, 105, 0.973);
--cui-color-orange: #f77f00;
--cui-color-table-border: rgba(146, 154, 158, 0.8);
- --cui-color-card-shadow: rgba(207, 192, 192, 0.2);
+ --cui-color-card-shadow: rgba(0, 0, 0, 0.8);
--text-medium-emphasis: rgba(255, 255, 255, 0.6);
+ --cui-emphasis-color-rgb: rgba(255, 255, 255, 0.6);
+ --cui-input-placeholder-color: rgba(255, 255, 255, 0.6);
+ .form-control {
+ border: 1px solid #121212;
+ }
+ //--cui-tertiary-bg: var(--cyberdrain-dark);
// Core UI Impact theme variables.
--cui-header-hover-color: var(--cyberdrain-light);
--cui-header-active-color: var(--cyberdrain-accent-blue);
@@ -417,28 +425,28 @@
--cui-body-color-rgb: var(--cyberdrain-light-rgb);
--cui-body-bg-rgb: var(--cyberdrain-dark-rgb);
--cui-btn-link-color: var(--cyberdrain-light);
- --cui-card-bg: var(--cyberdrain-dark);
--cui-card-border-color: var(--cyberdrain-accent-green);
- --cui-card-color: var(--cyberdrain-light);
+ --cui-card-color: rgba(255, 255, 255, 0.87);
--cui-options-card-border-color: var(--cyberdrain-accent-blue);
--cui-dropdown-bg: var(--cyberdrain-dark);
--cui-dropdown-color: var(--cyberdrain-light);
--cui-dropdown-border-color: var(--cyberdrain-accent-green);
--cui-dropdown-link-color: var(--cyberdrain-light);
- --cui-dropdown-link-hover-bg: var(--cyberdrain-accent-green);
+ --cui-dropdown-link-hover-bg: var(--cyberdrain-accent-blue);
--cui-dropdown-link-hover-color: var(--cyberdrain-light);
- --cui-footer-bg: var(--cyberdrain-dark);
+ --cui-footer-bg: #242424;
--cui-footer-color: var(--cyberdrain-light);
- --cui-footer-border-color: var(--cyberdrain-accent-green);
+ --cui-footer-border-color: #121212;
--cui-form-select-bg: var(--cyberdrain-dark);
--cui-form-select-border-color: var(--cyberdrain-light);
--cui-form-select-color: var(--cyberdrain-light);
+ --cui-form-check-bg: var(--cyberdrain-light);
--cui-header-bg: var(--cyberdrain-dark);
--cui-header-border-color: var(--cyberdrain-accent-green);
--cui-header-color: var(--cyberdrain-light);
--cui-header-divider-border-color: var(--cyberdrain-accent-green);
- --cui-input-bg: var(--cyberdrain-dark);
- --cui-input-border-colour: var(--cyberdrain-light);
+ --cui-input-bg: #2c2c2c;
+ --cui-input-border-colour: #121212;
--cui-input-color: var(--cyberdrain-light);
--cui-input-disabled-bg: var(--cyberdrain-darker);
--cui-input-disabled-border-color: var(--cyberdrain-lighter);
@@ -446,7 +454,7 @@
--cui-input-focus-bg: var(--cyberdrain-dark-striped);
--cui-input-focus-border-color: var(--cyberdrain-primary);
--cui-input-focus-color: var(--cyberdrain-primary);
- --cui-link-color: var(--cyberdrain-light);
+ --cui-link-color-rgb: var(--cyberdrain-light);
--cui-link-hover-color: var(--cyberdrain-primary);
--cui-list-group-bg: var(--cyberdrain-dark);
--cui-list-group-color: var(--cyberdrain-light);
@@ -467,15 +475,17 @@
--cui-nav-link-hover-color: var(--cyberdrain-light);
--cui-nav-tabs-border-color: var(--cyberdrain-accent-green);
--cui-nav-tabs-link-hover-border-color: var(--cyberdrain-accent-green);
- --cui-toast-background-color: var(--cui-color-header-bar);
--cui-toast-border-color: var(--cyberdrain-accent-green);
--cui-toast-color: var(--cui-color-black);
- --cui-toast-header-background-color: var(--cui-color-header-bar);
--cui-toast-header-color: var(--cui-color-black);
--cui-card-cap-color: var(--cyberdrain-white);
--cui-card-cap-bg: var(--cui-color-dark);
-
+ --cui-tertiary-bg: var(--cui-bgcolor-table-header);
// CIPP Impact theme variables.
+ --cipp-toast-bg: var(--cui-color-header-bar);
+ --cipp-toast-header-bg: var(--cui-color-header-bar);
+ --cipp-toast-color: var(--cui-color-black);
+
--cipp-fa-inverse-color: var(--cyberdrain-dark);
--cipp-search-bg: var(--cyberdrain-dark);
--cipp-search-border-color: var(--cyberdrain-accent-green);
@@ -492,7 +502,7 @@
--cipp-table-primary-colour: var(--cyberdrain-light);
--cipp-table-secondary-colour: var(--cui-gray-100);
--cipp-table-sort-focus-bg: var(--cyberdrain-secondary);
- --cipp-table-highlight-on-hover-bg: rgb(138, 136, 136);
+ // --cipp-table-highlight-on-hover-bg: rgb(138, 136, 136);
--cipp-table-highlight-on-hover-color: rgb(138, 136, 136);
--cipp-table-striped-bg: var(--cyberdrain-dark-striped);
--cipp-table-striped-colour: var(--cyberdrain-light-striped);
@@ -558,13 +568,27 @@
--cui-btn-disabled-color: #e55353;
--cui-btn-shadow: rgb(229 83 83 / 50%);
}
-
+ .accordion {
+ --cui-accordion-btn-icon: url('data:image/svg+xml,%3Cns0%3Asvg%20xmlns%3Ans0%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%20fill%3D%22%23FFFFFF%22%3E%3Cns0%3Apath%20fill-rule%3D%22evenodd%22%20d%3D%22M1%2E646%204%2E646a%2E5%2E5%200%200%201%20%2E708%200L8%2010%2E293l5%2E646-5%2E647a%2E5%2E5%200%200%201%20%2E708%2E708l-6%206a%2E5%2E5%200%200%201-%2E708%200l-6-6a%2E5%2E5%200%200%201%200-%2E708z%22%20%2F%3E%3C%2Fns0%3Asvg%3E');
+ }
.list-group-content-card {
--cui-list-group-border-color: var(--cyberdrain-accent-blue);
}
+ .card {
+ --cui-card-bg: rgba(40, 40, 40, 0.8);
+ }
+ .react-datepicker__header {
+ background-color: var(--cui-bgcolor-table-header);
+ }
+ .react-datepicker {
+ background-color: var(--cui-bgcolor-table-header);
+ }
+ .react-datepicker__time-list {
+ background-color: var(--cui-bgcolor-table-header);
+ }
.table {
- --cui-table-bg: var(--cyberdrain-dark);
+ --cui-table-bg: rgba(40, 40, 40, 0.8);
--cui-table-color: var(--cyberdrain-light);
--cui-table-striped-bg: var(--cyberdrain-dark-striped);
--cui-table-striped-color: var(--cyberdrain-light-striped);
diff --git a/src/scss/_variables.scss b/src/scss/_variables.scss
index 73f0201b5a22..7d77d8000e3b 100644
--- a/src/scss/_variables.scss
+++ b/src/scss/_variables.scss
@@ -86,10 +86,10 @@ $secondary-base: rgba(0, 48, 73) !default;
$secondary-50: rgba(0, 47, 73, 0.479) !default;
$secondary-25: rgba(0, 47, 73, 0.205) !default;
-$link-color: rgb(226, 92, 9) !default;
+//$link-color: var(--cui-link-color) !default;
$link-decoration: underline !default;
$link-shade-percentage: 20% !default;
-$link-hover-color: shift-color($link-color, $link-shade-percentage) !default;
+//$link-hover-color: rgba($link-color, $link-shade-percentage) !default;
$link-hover-decoration: null !default;
$search-bg-color: #ffffff;
@@ -318,7 +318,7 @@ $enable-shadows: true;
// $enable-rfs: true !default;
// $enable-validation-icons: true !default;
$enable-negative-margins: true;
-// $enable-deprecation-messages: true !default;
+$enable-deprecation-messages: false !default;
// $enable-important-utilities: true !default;
// $enable-contrast-ratio-correction: true !default;
// $enable-contrast-ratio-warnings: false !default;
@@ -741,7 +741,7 @@ $h5-font-size: $font-size-base;
// $btn-border-width: $input-btn-border-width !default;
// $btn-font-weight: $font-weight-normal !default;
-// $btn-box-shadow: inset 0 1px 0 rgba($white, .15), 0 1px 1px rgba($black, .075) !default;
+$btn-box-shadow: none !default;
// $btn-focus-width: $input-btn-focus-width !default;
// $btn-focus-box-shadow: $input-btn-focus-box-shadow !default;
// $btn-disabled-opacity: .65 !default;
@@ -870,7 +870,7 @@ $h5-font-size: $font-size-base;
// $form-check-input-active-filter: brightness(90%) !default;
-// $form-check-input-bg: $input-bg !default;
+$form-check-input-bg: var(--cyberdrain-light) !default;
// $form-check-input-border: 1px solid rgba($black, .25) !default;
// $form-check-input-border-radius: .25em !default;
// $form-check-radio-border-radius: 50% !default;
@@ -1252,7 +1252,8 @@ $h5-font-size: $font-size-base;
// $accordion-button-padding-y: $accordion-padding-y !default;
// $accordion-button-padding-x: $accordion-padding-x !default;
// $accordion-button-color: $accordion-color !default;
-// $accordion-button-bg: $accordion-bg !default;
+$accordion-button-bg: var(--cipp-accordion-button-bg) !default;
+
// $accordion-transition: $btn-transition, border-radius .15s ease !default;
// $accordion-button-active-bg: tint-color($component-active-bg, 90%) !default;
// $accordion-button-active-color: shade-color($primary, 10%) !default;
@@ -1275,7 +1276,7 @@ $h5-font-size: $font-size-base;
// scss-docs-start tooltip-variables
// $tooltip-font-size: $font-size-sm !default;
// $tooltip-max-width: 200px !default;
-// $tooltip-color: $high-emphasis-inverse !default;
+$tooltip-color: var(--cyberdrain-light) !default;
// $tooltip-bg: $black !default;
// $tooltip-border-radius: $border-radius !default;
// $tooltip-opacity: .9 !default;
@@ -1331,16 +1332,15 @@ $h5-font-size: $font-size-base;
// $toast-padding-x: .75rem !default;
// $toast-padding-y: .5rem !default;
// $toast-font-size: .875rem !default;
-// $toast-color: unset !default;
-// $toast-background-color: rgba($white, .85) !default;
+$toast-color: var(--cipp-toast-color) !default;
// $toast-border-width: 1px !default;
// $toast-border-color: rgba(0, 0, 0, .1) !default;
// $toast-border-radius: $border-radius !default;
// $toast-box-shadow: $box-shadow !default;
// $toast-spacing: $container-padding-x !default;
-
-// $toast-header-color: $gray-600 !default;
-// $toast-header-background-color: rgba($white, .85) !default;
+$toast-background-color: var(--cipp-toast-bg);
+$toast-header-background-color: var(--cipp-toast-header-bg);
+$toast-header-color: var(--cipp-toast-color) !default;
// $toast-header-border-color: rgba(0, 0, 0, .05) !default;
// scss-docs-end toast-variables
@@ -1484,7 +1484,7 @@ $h5-font-size: $font-size-base;
// List group
// scss-docs-start list-group-variables
// $list-group-color: unset !default;
-// $list-group-bg: $white !default;
+$list-group-bg: var(--cui-color-white) !default;
// $list-group-border-color: rgba($black, .125) !default;
// $list-group-border-width: $border-width !default;
// $list-group-border-radius: $border-radius !default;
@@ -1659,7 +1659,7 @@ $sidebar-width: 17rem !default;
// $sidebar-padding-y: 0 !default;
// $sidebar-padding-x: 0 !default;
// $sidebar-color: $high-emphasis-inverse !default;
-$sidebar-bg: rgb(240,240,240) !default;
+$sidebar-bg: rgb(240, 240, 240) !default;
// $sidebar-border-width: 0 !default;
// $sidebar-border-color: transparent !default;
// $sidebar-transition: margin-left .15s, margin-right .15s, box-shadow .075s, transform .15s, width .15s, z-index 0s ease .15s !default;
@@ -1739,7 +1739,7 @@ $sidebar-nav-link-active-icon-color: rgba(247, 127, 0) !default;
// Footer
// scss-docs-start footer-variables
-// $footer-min-height: 3rem !default;
+$footer-min-height: 4rem !default;
// $footer-padding-y: $spacer / 2 !default;
// $footer-padding-x: $spacer !default;
// $footer-bg: $gray-100 !default;
diff --git a/src/scss/style.scss b/src/scss/style.scss
index 42c5106d5ab2..1393a9feccce 100644
--- a/src/scss/style.scss
+++ b/src/scss/style.scss
@@ -3,15 +3,12 @@
$enable-ltr: true;
$enable-rtl: true;
+@import 'tenantselector';
+// If you want to add custom CSS you can put it here.
// Import CoreUI for React components library
-@import '~@coreui/coreui/scss/coreui';
-// Import Chart.js custom tooltips styles
-@import '~@coreui/chartjs/scss/coreui-chartjs';
+@import '@coreui/coreui/scss/coreui';
@import 'layout';
-
-// If you want to add custom CSS you can put it here.
-@import 'themes';
-@import 'tenantselector';
@import 'custom';
+@import 'themes';
diff --git a/src/serviceWorker.js b/src/serviceWorker.js
deleted file mode 100644
index d4f72f2c4b98..000000000000
--- a/src/serviceWorker.js
+++ /dev/null
@@ -1,123 +0,0 @@
-// In production, we register a service worker to serve assets from local cache.
-
-// This lets the app load faster on subsequent visits in production, and gives
-// it offline capabilities. However, it also means that developers (and users)
-// will only see deployed updates on the "N+1" visit to a page, since previously
-// cached resources are updated in the background.
-
-// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
-// This link also includes instructions on opting out of this behavior.
-
-const isLocalhost = Boolean(
- window.location.hostname === 'localhost' ||
- // [::1] is the IPv6 localhost address.
- window.location.hostname === '[::1]' ||
- // 127.0.0.1/8 is considered localhost for IPv4.
- window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/),
-)
-
-export function register(config) {
- if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
- // The URL constructor is available in all browsers that support SW.
- const publicUrl = new URL(process.env.PUBLIC_URL, window.location)
- if (publicUrl.origin !== window.location.origin) {
- // Our service worker won't work if PUBLIC_URL is on a different origin
- // from what our page is served on. This might happen if a CDN is used to
- // serve assets; see https://github.com/facebook/create-react-app/issues/2374
- return
- }
-
- window.addEventListener('load', () => {
- const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
-
- if (isLocalhost) {
- // This is running on localhost. Let's check if a service worker still exists or not.
- checkValidServiceWorker(swUrl, config)
-
- // Add some additional logging to localhost, pointing developers to the
- // service worker/PWA documentation.
- navigator.serviceWorker.ready.then(() => {
- console.log(
- 'This web app is being served cache-first by a service ' +
- 'worker. To learn more, visit https://goo.gl/SC7cgQ',
- )
- })
- } else {
- // Is not local host. Just register service worker
- registerValidSW(swUrl, config)
- }
- })
- }
-}
-
-function registerValidSW(swUrl, config) {
- navigator.serviceWorker
- .register(swUrl)
- .then((registration) => {
- registration.onupdatefound = () => {
- const installingWorker = registration.installing
- installingWorker.onstatechange = () => {
- if (installingWorker.state === 'installed') {
- if (navigator.serviceWorker.controller) {
- // At this point, the old content will have been purged and
- // the fresh content will have been added to the cache.
- // It's the perfect time to display a "New content is
- // available; please refresh." message in your web app.
- console.log('New content is available; please refresh.')
-
- // Execute callback
- if (config.onUpdate) {
- config.onUpdate(registration)
- }
- } else {
- // At this point, everything has been precached.
- // It's the perfect time to display a
- // "Content is cached for offline use." message.
- console.log('Content is cached for offline use.')
-
- // Execute callback
- if (config.onSuccess) {
- config.onSuccess(registration)
- }
- }
- }
- }
- }
- })
- .catch((error) => {
- console.error('Error during service worker registration:', error)
- })
-}
-
-function checkValidServiceWorker(swUrl, config) {
- // Check if the service worker can be found. If it can't reload the page.
- fetch(swUrl)
- .then((response) => {
- // Ensure service worker exists, and that we really are getting a JS file.
- if (
- response.status === 404 ||
- response.headers.get('content-type').indexOf('javascript') === -1
- ) {
- // No service worker found. Probably a different app. Reload the page.
- navigator.serviceWorker.ready.then((registration) => {
- registration.unregister().then(() => {
- window.location.reload()
- })
- })
- } else {
- // Service worker found. Proceed as normal.
- registerValidSW(swUrl, config)
- }
- })
- .catch(() => {
- console.log('No internet connection found. App is running in offline mode.')
- })
-}
-
-export function unregister() {
- if ('serviceWorker' in navigator) {
- navigator.serviceWorker.ready.then((registration) => {
- registration.unregister()
- })
- }
-}
diff --git a/src/store/api/baseQuery.js b/src/store/api/baseQuery.js
index b999c8b6b272..493b6d012bab 100644
--- a/src/store/api/baseQuery.js
+++ b/src/store/api/baseQuery.js
@@ -23,7 +23,6 @@ export const axiosQuery = async ({ path, method = 'get', params, data, hideToast
}
}
export function abortRequestSafe() {
- console.log(newController)
newController.abort()
newController = new AbortController()
}
diff --git a/src/store/api/users.js b/src/store/api/users.js
index 66cfd9bd48c6..8dc443ce1dc1 100644
--- a/src/store/api/users.js
+++ b/src/store/api/users.js
@@ -59,7 +59,11 @@ export const usersApi = baseApi.injectEndpoints({
queryFn: async (_args, _baseQueryApi, _options, baseQuery) => {
const startRequest = await baseQuery({
path: '/api/execBECCheck',
- params: { userId: _args.userId, tenantFilter: _args.tenantFilter },
+ params: {
+ userId: _args.userId,
+ tenantFilter: _args.tenantFilter,
+ userName: _args.userName,
+ },
})
if (startRequest.error) {
return { error: startRequest.error }
diff --git a/src/store/middleware/errorMiddleware.js b/src/store/middleware/errorMiddleware.js
index 740027f89dc3..d09c48915ff3 100644
--- a/src/store/middleware/errorMiddleware.js
+++ b/src/store/middleware/errorMiddleware.js
@@ -17,7 +17,21 @@ export const errorMiddleware =
action.payload.data =
'The Azure Function has taken too long to respond. Try selecting a different report or a single tenant instead'
}
- const message = action.payload?.data || 'A generic error has occurred.'
+ //if the payload is a string, show the string, if the payload is an object, check if there is a 'Results or 'results' or 'result' property and show that, otherwise show the whole object
+ let message = action.payload?.data || 'A generic error has occurred.'
+ if (typeof message === 'string') {
+ // Do nothing, message is already a string
+ } else if (typeof message === 'object') {
+ if (message.Results) {
+ message = message.Results
+ } else if (message.results) {
+ message = message.results
+ } else if (message.result) {
+ message = message.result
+ } else {
+ message = JSON.stringify(message)
+ }
+ }
if (message.length > 240) {
message = message.substring(0, 240) + '...'
}
diff --git a/src/store/store.js b/src/store/store.js
index 831ab0eb792b..b50a0c6eb2b5 100644
--- a/src/store/store.js
+++ b/src/store/store.js
@@ -15,8 +15,8 @@ export const store = configureStore({
})
// enable redux module hot reload
-if (process.env.NODE_ENV !== 'production' && module.hot) {
- module.hot.accept('./root', () => store.replaceReducer(rootReducer))
+if (import.meta.env.NODE_ENV !== 'production' && import.meta.hot) {
+ import.meta.hot.accept('./root', () => store.replaceReducer(rootReducer))
}
export const persistor = persistStore(store)
diff --git a/src/views/cipp/AppApproval.js b/src/views/cipp/AppApproval.jsx
similarity index 99%
rename from src/views/cipp/AppApproval.js
rename to src/views/cipp/AppApproval.jsx
index ab13d99f8c1d..e23081ec1dc0 100644
--- a/src/views/cipp/AppApproval.js
+++ b/src/views/cipp/AppApproval.jsx
@@ -14,6 +14,7 @@ import {
RFFSelectSearch,
} from 'src/components/forms'
import { useLazyGenericPostRequestQuery } from 'src/store/api/app'
+
const Error = ({ name }) => (
{
{!postResults.isSuccess && (
+ {/* eslint-disable react/prop-types */}
{(props) => {
return (
<>
diff --git a/src/views/cipp/CIPPSettings.js b/src/views/cipp/CIPPSettings.jsx
similarity index 79%
rename from src/views/cipp/CIPPSettings.js
rename to src/views/cipp/CIPPSettings.jsx
index 965ae5beef54..4c74b28bbcc9 100644
--- a/src/views/cipp/CIPPSettings.js
+++ b/src/views/cipp/CIPPSettings.jsx
@@ -37,6 +37,7 @@ import {
import {
useExecAddExcludeTenantMutation,
useExecRemoveExcludeTenantMutation,
+ useListTenantsQuery,
} from 'src/store/api/tenants'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {
@@ -50,7 +51,6 @@ import {
faScroll,
faTrash,
} from '@fortawesome/free-solid-svg-icons'
-import { useListTenantsQuery } from 'src/store/api/tenants'
import { useLazyEditDnsConfigQuery, useLazyGetDnsConfigQuery } from 'src/store/api/domains'
import { useDispatch, useSelector } from 'react-redux'
import {
@@ -78,14 +78,31 @@ import {
TenantSelectorMultiple,
} from 'src/components/utilities'
import CippListOffcanvas from 'src/components/utilities/CippListOffcanvas'
-import { TitleButton } from 'src/components/buttons'
+import { TitleButton, TableModalButton } from 'src/components/buttons'
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 PropTypes from 'prop-types'
+
+function Lazy({ visible, children }) {
+ const rendered = useRef(visible)
+
+ if (visible && !rendered.current) {
+ rendered.current = true
+ }
+
+ if (!rendered.current) return null
+
+ return {children}
+}
+
+Lazy.propTypes = {
+ visible: PropTypes.bool,
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
+}
const CIPPSettings = () => {
const [active, setActive] = useState(1)
@@ -119,28 +136,43 @@ const CIPPSettings = () => {
-
+
+
+
-
+
+
+
-
+
+
+
+
-
+
+
+
-
+
+
+
-
+
+
+
-
+
+
+
@@ -191,7 +223,7 @@ const GeneralSettings = () => {
{
name: 'Missing GDAP Roles',
selector: (row) => row?.MissingRoles,
- cell: cellTableFormatter('MissingRoles', true, false),
+ cell: cellTableFormatter('MissingRoles', true, false, true),
},
{
name: 'Roles available',
@@ -334,6 +366,7 @@ const GeneralSettings = () => {
console.log(e)
setShowExtendedInfo(!e.target.checked)
}}
+ key={'Show Extended Info'}
/>,
],
}
@@ -346,17 +379,16 @@ const GeneralSettings = () => {
-
+
-
- Permissions Check
-
+
- Click the button below to start a permissions check.
+ Permissions Check
+ Click the button below to start a permissions check.
checkPermissions()}
disabled={permissionsResult.isFetching}
- className="mt-3"
+ className="mb-3 me-2"
>
{permissionsResult.isFetching && (
@@ -365,6 +397,22 @@ const GeneralSettings = () => {
{permissionsResult.isSuccess && (
<>
+ {permissionsResult.data.Results?.AccessTokenDetails?.Name !== '' && (
+ <>
+ setTokenOffcanvasVisible(true)}>
+ Details
+
+ setTokenOffcanvasVisible(false)}
+ />
+ >
+ )}
@@ -381,6 +429,7 @@ const GeneralSettings = () => {
documentation on how to add permissions{' '}
here
@@ -394,53 +443,79 @@ const GeneralSettings = () => {
>
)}
- {permissionsResult.data.Results?.AccessTokenDetails?.Name !== '' && (
- <>
- setTokenOffcanvasVisible(true)}>
- Details
-
- setTokenOffcanvasVisible(false)}
- />
- >
- )}
>
)}
-
+
-
- GDAP Check
-
+
- Click the button below to start a check for general GDAP settings.
+ GDAP Check
+ Click the button below to start a check for general GDAP settings.
checkGDAP({ path: '/api/ExecAccessChecks?GDAP=true' })}
disabled={GDAPResult.isFetching}
- className="mt-3"
+ className="mb-3 me-2"
>
{GDAPResult.isFetching && (
)}
Run GDAP Check
+ {GDAPResult.isSuccess && (
+ <>
+ p['@odata.type'] == '#microsoft.graph.group',
+ )}
+ title="Groups"
+ />
+ p['@odata.type'] == '#microsoft.graph.directoryRole',
+ )}
+ title="Roles"
+ />
+ >
+ )}
{GDAPResult.isSuccess && GDAPResult.data.Results.GDAPIssues?.length > 0 && (
-
+ <>
+ {GDAPResult.data.Results.GDAPIssues?.filter((e) => e.Type === 'Error')
+ .length > 0 && (
+
+ Relationship errors detected. Review the table below for more details.
+
+ )}
+ {GDAPResult.data.Results.GDAPIssues?.filter((e) => e.Type === 'Warning')
+ .length > 0 && (
+
+ Relationship warnings detected. Review the table below for more details.
+
+ )}
+
+ >
)}
{GDAPResult.isSuccess && GDAPResult.data.Results.GDAPIssues?.length === 0 && (
@@ -448,23 +523,6 @@ const GeneralSettings = () => {
Tenant Access Check if you are experiencing issues.
)}
- {GDAPResult.isSuccess && (
- <>
- p['@odata.type'] == '#microsoft.graph.group',
- )}
- title="Groups"
- />
- p['@odata.type'] == '#microsoft.graph.directoryRole',
- )}
- title="Roles"
- />
- >
- )}
@@ -474,10 +532,9 @@ const GeneralSettings = () => {
-
- Tenant Access Check
-
+
+ Tenant Access Check
@@ -564,12 +621,14 @@ const ExcludedTenantsSettings = () => {
onConfirm: () => removeExcludeTenant(domain),
})
- const handleCPVPermissions = (domain) =>
+ const handleCPVPermissions = (domain, resetsp = false) =>
ModalService.confirm({
title: 'Refresh Permissions',
body:
Are you sure you want to refresh permissions for {domain.defaultDomainName}?
,
onConfirm: () =>
- refreshPermissions({ path: `/api/ExecCPVPermissions?TenantFilter=${domain.customerId}` }),
+ refreshPermissions({
+ path: `/api/ExecCPVPermissions?TenantFilter=${domain.customerId}&ResetSP=${resetsp}`,
+ }),
})
const handleConfirmExcludeTenant = (tenant) => {
ModalService.confirm({
@@ -577,7 +636,6 @@ const ExcludedTenantsSettings = () => {
body:
Are you sure you want to exclude this tenant?
,
onConfirm: () => addExcludeTenant(tenant),
})
-
.unwrap()
.then(() => {
dispatch(setCurrentTenant({}))
@@ -606,6 +664,7 @@ const ExcludedTenantsSettings = () => {
Add Excluded Tenant
)
+
function StatusIcon(graphErrorCount) {
if (graphErrorCount > 0) {
return
@@ -650,7 +709,12 @@ const ExcludedTenantsSettings = () => {
)}
- handleCPVPermissions(row)}>
+ handleCPVPermissions(row, false)}
+ >
@@ -672,13 +736,6 @@ const ExcludedTenantsSettings = () => {
cell: (row) => CellTip(row['defaultDomainName']),
exportSelector: 'defaultDomainName',
},
- {
- name: 'Relationship Type',
- selector: (row) => row['delegatedPrivilegeStatus'],
- sortable: true,
- cell: (row) => CellDelegatedPrivilege({ cell: row['delegatedPrivilegeStatus'] }),
- exportSelector: 'delegatedPrivilegeStatus',
- },
{
name: 'Excluded',
selector: (row) => row['Excluded'],
@@ -768,11 +825,25 @@ 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,
filterlist: [
- { filterName: 'Excluded Tenants', filter: '"Excluded":true' },
- { filterName: 'Included Tenants', filter: '"Excluded":false' },
+ {
+ filterName: 'Excluded Tenants',
+ filter: 'Complex: Excluded eq true',
+ },
+ {
+ filterName: 'Included Tenants',
+ filter: 'Complex: Excluded eq false',
+ },
],
keyField: 'id',
columns,
@@ -1084,18 +1155,36 @@ const NotificationsSettings = () => {
{ value: 'Updates', name: 'Updates Status' },
{ value: 'Standards', name: 'All Standards' },
{ value: 'TokensUpdater', name: 'Token Events' },
- { value: 'ExecDnsConfig', name: 'Changing DNS Settings' },
- { value: 'ExecExcludeLicenses', name: 'Adding excluded licenses' },
- { value: 'ExecExcludeTenant', name: 'Adding excluded tenants' },
+ {
+ value: 'ExecDnsConfig',
+ name: 'Changing DNS Settings',
+ },
+ {
+ value: 'ExecExcludeLicenses',
+ name: 'Adding excluded licenses',
+ },
+ {
+ value: 'ExecExcludeTenant',
+ name: 'Adding excluded tenants',
+ },
{ value: 'EditUser', name: 'Editing a user' },
- { value: 'ChocoApp', name: 'Adding or deploying applications' },
- { value: 'AddAPDevice', name: 'Adding autopilot devices' },
+ {
+ value: 'ChocoApp',
+ name: 'Adding or deploying applications',
+ },
+ {
+ value: 'AddAPDevice',
+ name: 'Adding autopilot devices',
+ },
{ value: 'EditTenant', name: 'Editing a tenant' },
{ value: 'AddMSPApp', name: 'Adding an MSP app' },
{ value: 'AddUser', name: 'Adding a user' },
{ value: 'AddGroup', name: 'Adding a group' },
{ value: 'NewTenant', name: 'Adding a tenant' },
- { value: 'ExecOffboardUser', name: 'Executing the offboard wizard' },
+ {
+ value: 'ExecOffboardUser',
+ name: 'Executing the offboard wizard',
+ },
]}
/>
@@ -1368,15 +1457,13 @@ const DNSSettings = () => {
{getDnsConfigResult.isUninitialized && getDnsConfig()}
{getDnsConfigResult.isSuccess && (
-
- Application Settings
-
+
-
+
-
+
DNS Resolver
{resolvers.map((r, index) => (
@@ -1400,18 +1487,18 @@ const DNSSettings = () => {
)}
-
+
Frontend Version
Latest: {isSuccessVersion ? versions.RemoteCIPPVersion : }
- Current: {isSuccessVersion ? versions.LocalCIPPVersion : }
+
+ Current: {isSuccessVersion ? versions.LocalCIPPVersion : }
+
-
-
-
+
Clear Caches
{
{clearCacheResult.data?.Results}
)}
-
-
+
Settings Backup
{
>
)}
-
+
Backend API Version
Latest: {isSuccessVersion ? versions.RemoteCIPPAPIVersion : }
- Current: {isSuccessVersion ? versions.LocalCIPPAPIVersion : }
+
+ Current: {isSuccessVersion ? versions.LocalCIPPAPIVersion : }
+
@@ -1533,9 +1621,7 @@ const ExtensionsTab = () => {
Results
- <>
- {listSyncExtensionResult.data.Results}
- >
+ {listSyncExtensionResult.data.Results}
)}
@@ -1565,8 +1651,8 @@ const ExtensionsTab = () => {
)}
- {Extensions.map((integration) => (
-
+ {Extensions.map((integration, idx) => (
+
{integration.name}
@@ -1582,9 +1668,9 @@ const ExtensionsTab = () => {
{integration.SettingOptions.map(
- (integrationOptions) =>
+ (integrationOptions, idx) =>
integrationOptions.type === 'input' && (
-
+
{
),
)}
{integration.SettingOptions.map(
- (integrationOptions) =>
+ (integrationOptions, idx) =>
integrationOptions.type === 'checkbox' && (
-
+
{
const MappingsTab = () => {
const [listHaloBackend, listBackendHaloResult = []] = useLazyGenericGetRequestQuery()
+ const [listNinjaOrgsBackend, listBackendNinjaOrgsResult] = useLazyGenericGetRequestQuery()
+ const [listNinjaFieldsBackend, listBackendNinjaFieldsResult] = useLazyGenericGetRequestQuery()
const [setHaloExtensionconfig, extensionHaloConfigResult = []] = useLazyGenericPostRequestQuery()
+ const [setNinjaOrgsExtensionconfig, extensionNinjaOrgsConfigResult] =
+ useLazyGenericPostRequestQuery()
+ const [setNinjaOrgsExtensionAutomap, extensionNinjaOrgsAutomapResult] =
+ useLazyGenericPostRequestQuery()
+ const [setNinjaFieldsExtensionconfig, extensionNinjaFieldsConfigResult] =
+ useLazyGenericPostRequestQuery()
const onHaloSubmit = (values) => {
setHaloExtensionconfig({
@@ -1685,10 +1779,38 @@ const MappingsTab = () => {
values: { mappings: values },
})
}
+ const onNinjaOrgsSubmit = (values) => {
+ setNinjaOrgsExtensionconfig({
+ path: 'api/ExecExtensionMapping?AddMapping=NinjaOrgs',
+ values: { mappings: values },
+ })
+ }
+
+ const onNinjaOrgsAutomap = async (values) => {
+ await setNinjaOrgsExtensionAutomap({
+ path: 'api/ExecExtensionMapping?AutoMapping=NinjaOrgs',
+ values: { mappings: values },
+ })
+ await listNinjaOrgsBackend({
+ path: 'api/ExecExtensionMapping?List=NinjaOrgs',
+ })
+ }
+
+ const onNinjaFieldsSubmit = (values) => {
+ setNinjaFieldsExtensionconfig({
+ path: 'api/ExecExtensionMapping?AddMapping=NinjaFields',
+
+ values: { mappings: values },
+ })
+ }
return (
{listBackendHaloResult.isUninitialized &&
listHaloBackend({ path: 'api/ExecExtensionMapping?List=Halo' })}
+ {listBackendNinjaOrgsResult.isUninitialized &&
+ listNinjaOrgsBackend({ path: 'api/ExecExtensionMapping?List=NinjaOrgs' })}
+ {listBackendNinjaFieldsResult.isUninitialized &&
+ listNinjaFieldsBackend({ path: 'api/ExecExtensionMapping?List=NinjaFields' })}
<>
@@ -1742,6 +1864,154 @@ const MappingsTab = () => {
)}
+
+
+ NinjaOne Field Mapping Table
+
+
+ {listBackendNinjaFieldsResult.isFetching ? (
+
+ ) : (
+
+
+
+
+ NinjaOne Organization Mapping Table
+
+
+ {listBackendNinjaOrgsResult.isFetching ? (
+
+ ) : (
+
+
>
)
diff --git a/src/views/cipp/Logs.js b/src/views/cipp/Logs.jsx
similarity index 100%
rename from src/views/cipp/Logs.js
rename to src/views/cipp/Logs.jsx
diff --git a/src/views/cipp/Scheduler.js b/src/views/cipp/Scheduler.jsx
similarity index 81%
rename from src/views/cipp/Scheduler.js
rename to src/views/cipp/Scheduler.jsx
index af533a68b800..dc886acac1aa 100644
--- a/src/views/cipp/Scheduler.js
+++ b/src/views/cipp/Scheduler.jsx
@@ -1,20 +1,14 @@
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 { useSelector } from 'react-redux'
import { Field, Form, FormSpy } from 'react-final-form'
import {
- Condition,
- RFFCFormCheck,
RFFCFormInput,
RFFCFormInputArray,
- RFFCFormRadio,
RFFCFormSwitch,
- RFFCFormTextarea,
RFFSelectSearch,
} from 'src/components/forms'
-import countryList from 'src/data/countryList'
-
import {
useGenericGetRequestQuery,
useLazyGenericGetRequestQuery,
@@ -24,13 +18,7 @@ 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 { cellBadgeFormatter, 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'
@@ -39,61 +27,65 @@ import { ModalService, TenantSelector } from 'src/components/utilities'
import CippCodeOffCanvas from 'src/components/utilities/CippCodeOffcanvas'
import arrayMutators from 'final-form-arrays'
-const Offcanvas = (row, rowIndex, formatExtraData) => {
+const Scheduler = () => {
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
- }
+ const Offcanvas = (row, rowIndex, formatExtraData) => {
+ const [ocVisible, setOCVisible] = useState(false)
- 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 handleDeleteSchedule = (apiurl, message) => {
+ ModalService.confirm({
+ title: 'Confirm',
+ body: {message}
,
+ onConfirm: () =>
+ ExecuteGetRequest({ path: apiurl }).then((res) => {
+ setRefreshState(res.requestId)
+ }),
+ 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)
@@ -276,6 +268,7 @@ const Scheduler = () => {
+ {/* eslint-disable react/prop-types */}
{(props) => {
const selectedCommand = availableCommands.find(
(cmd) => cmd.Function === props.values.command?.value,
@@ -289,6 +282,7 @@ const Scheduler = () => {
+ {/* eslint-disable react/prop-types */}
{(props) => {
const selectedCommand = availableCommands.find(
(cmd) => cmd.Function === props.values.command?.value,
@@ -381,6 +375,19 @@ const Scheduler = () => {
{postResults.data.Results}
)}
+ {getResults.isFetching && (
+
+ Loading
+
+ )}
+ {getResults.isSuccess && (
+ {getResults.data?.Results}
+ )}
+ {getResults.isError && (
+
+ Could not connect to API: {getResults.error.message}
+
+ )}
)
}}
@@ -390,7 +397,10 @@ const Scheduler = () => {
{
],
},
filterlist: [
- { filterName: 'Planned Jobs', filter: 'Complex: TaskState eq Planned' },
- { filterName: 'Completed Jobs', filter: 'Complex: TaskState eq Completed' },
- { filterName: 'Recurring Jobs', filter: 'Complex: Recurrence gt 0' },
- { filterName: 'One-time Jobs', filter: 'Complex: Recurrence eq 0' },
+ {
+ filterName: 'Planned Jobs',
+ filter: 'Complex: TaskState eq Planned',
+ },
+ {
+ filterName: 'Completed Jobs',
+ filter: 'Complex: TaskState eq Completed',
+ },
+ {
+ filterName: 'Recurring Jobs',
+ filter: 'Complex: Recurrence gt 0',
+ },
+ {
+ filterName: 'One-time Jobs',
+ filter: 'Complex: Recurrence eq 0',
+ },
],
keyField: 'id',
columns,
diff --git a/src/views/cipp/Setup.js b/src/views/cipp/Setup.jsx
similarity index 99%
rename from src/views/cipp/Setup.js
rename to src/views/cipp/Setup.jsx
index e8d9a33d3064..6b674870df54 100644
--- a/src/views/cipp/Setup.js
+++ b/src/views/cipp/Setup.jsx
@@ -21,12 +21,14 @@ function useInterval(callback, delay, state) {
function tick() {
savedCallback.current()
}
+
if (delay !== null) {
let id = setInterval(tick, delay)
return () => clearInterval(id)
}
}, [delay, state])
}
+
const Error = ({ name }) => (
{
<>
-
+
Refresh Graph Token
diff --git a/src/views/cipp/UserSettings.js b/src/views/cipp/UserSettings.jsx
similarity index 100%
rename from src/views/cipp/UserSettings.js
rename to src/views/cipp/UserSettings.jsx
diff --git a/src/views/email-exchange/administration/AddContact.js b/src/views/email-exchange/administration/AddContact.jsx
similarity index 97%
rename from src/views/email-exchange/administration/AddContact.js
rename to src/views/email-exchange/administration/AddContact.jsx
index c6b323fd3467..9a75405d2ba1 100644
--- a/src/views/email-exchange/administration/AddContact.js
+++ b/src/views/email-exchange/administration/AddContact.jsx
@@ -43,7 +43,7 @@ const AddContact = () => {
onSubmit={onSubmit}
render={({ handleSubmit, submitting, values }) => {
return (
-
+
diff --git a/src/views/email-exchange/administration/AddSharedMailbox.js b/src/views/email-exchange/administration/AddSharedMailbox.jsx
similarity index 100%
rename from src/views/email-exchange/administration/AddSharedMailbox.js
rename to src/views/email-exchange/administration/AddSharedMailbox.jsx
diff --git a/src/views/email-exchange/administration/ContactsList.js b/src/views/email-exchange/administration/ContactsList.jsx
similarity index 99%
rename from src/views/email-exchange/administration/ContactsList.js
rename to src/views/email-exchange/administration/ContactsList.jsx
index 21aa657ee0c4..094f3c6930f3 100644
--- a/src/views/email-exchange/administration/ContactsList.js
+++ b/src/views/email-exchange/administration/ContactsList.jsx
@@ -7,6 +7,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faEdit, faEllipsisV } from '@fortawesome/free-solid-svg-icons'
import { TitleButton } from 'src/components/buttons'
import { CippActionsOffcanvas } from 'src/components/utilities'
+
const Actions = (row, rowIndex, formatExtraData) => {
const tenant = useSelector((state) => state.app.currentTenant)
const [ocVisible, setOCVisible] = useState(false)
@@ -14,6 +15,7 @@ const Actions = (row, rowIndex, formatExtraData) => {
<>
diff --git a/src/views/email-exchange/administration/EditCalendarPermissions.js b/src/views/email-exchange/administration/EditCalendarPermissions.jsx
similarity index 97%
rename from src/views/email-exchange/administration/EditCalendarPermissions.js
rename to src/views/email-exchange/administration/EditCalendarPermissions.jsx
index fc63616542a1..589b5f3c248c 100644
--- a/src/views/email-exchange/administration/EditCalendarPermissions.js
+++ b/src/views/email-exchange/administration/EditCalendarPermissions.jsx
@@ -174,6 +174,8 @@ const EditCalendarPermission = () => {
{ value: 'PublishingAuthor', name: 'Publishing Author' },
{ value: 'PublishingEditor', name: 'Publishing Editor' },
{ value: 'Reviewer', name: 'Reviewer' },
+ { value: 'LimitedDetails', name: 'Limited Details' },
+ { value: 'AvailabilityOnly', name: 'Availability Only' },
]}
placeholder="Select a permission level"
name="Permissions"
diff --git a/src/views/email-exchange/administration/EditContact.js b/src/views/email-exchange/administration/EditContact.jsx
similarity index 100%
rename from src/views/email-exchange/administration/EditContact.js
rename to src/views/email-exchange/administration/EditContact.jsx
diff --git a/src/views/email-exchange/administration/EditMailboxPermissions.js b/src/views/email-exchange/administration/EditMailboxPermissions.jsx
similarity index 87%
rename from src/views/email-exchange/administration/EditMailboxPermissions.js
rename to src/views/email-exchange/administration/EditMailboxPermissions.jsx
index fd41f5af70e1..ddd87082fe47 100644
--- a/src/views/email-exchange/administration/EditMailboxPermissions.js
+++ b/src/views/email-exchange/administration/EditMailboxPermissions.jsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react'
+import React, { useEffect, useRef, useState } from 'react'
import {
CButton,
CCallout,
@@ -18,8 +18,7 @@ import {
import useQuery from 'src/hooks/useQuery'
import { useDispatch } from 'react-redux'
import { Form, Field } from 'react-final-form'
-import { RFFSelectSearch, RFFCFormSelect, RFFCFormCheck, RFFCFormInput } from 'src/components/forms'
-import { useListUsersQuery } from 'src/store/api/users'
+import { RFFSelectSearch, RFFCFormCheck, RFFCFormInput, RFFCFormSwitch } from 'src/components/forms'
import { ModalService } from 'src/components/utilities'
import {
useLazyGenericPostRequestQuery,
@@ -28,32 +27,38 @@ import {
} from 'src/store/api/app'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
-import {
- useListMailboxPermissionsQuery,
- useListMailboxesQuery,
- useListCalendarPermissionsQuery,
-} from 'src/store/api/mailbox'
-import { CippTable } from 'src/components/tables'
-import { useListMailboxDetailsQuery } from 'src/store/api/mailbox'
-import { CellBoolean } from 'src/components/tables'
+import { useListMailboxDetailsQuery, useListMailboxPermissionsQuery } from 'src/store/api/mailbox'
+import { CellBoolean, CippDatatable } from 'src/components/tables'
import DatePicker from 'react-datepicker'
import 'react-datepicker/dist/react-datepicker.css'
-import { RFFCFormSwitch } from 'src/components/forms'
+import PropTypes from 'prop-types'
const formatter = (cell, warning = false, reverse = false, colourless = false) =>
CellBoolean({ cell, warning, reverse, colourless })
+function Lazy({ visible, children }) {
+ const rendered = useRef(visible)
+
+ if (visible && !rendered.current) {
+ rendered.current = true
+ }
+
+ if (!rendered.current) return null
+
+ return {children}
+}
+
+Lazy.propTypes = {
+ visible: PropTypes.bool,
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
+}
+
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',
@@ -100,12 +105,6 @@ const MailboxSettings = () => {
},
]
- const {
- data: user = {},
- isFetching: userIsFetching,
- error: userError,
- } = useListMailboxPermissionsQuery({ tenantDomain, userId })
-
return (
@@ -129,16 +128,24 @@ const MailboxSettings = () => {
-
+
+
+
-
+
+
+
-
+
+
+
-
+
+
+
@@ -151,20 +158,20 @@ const MailboxSettings = () => {
{active === 1 && (
- <>
- {userIsFetching && }
- {!userIsFetching && (
-
- )}
- >
+
)}
{active === 2 && (
- <>
- {usercalIsFetching && }
- {!usercalIsFetching && (
-
- )}
- >
+
)}
{active === 3 && (
<>
@@ -200,7 +207,15 @@ const MailboxPermissions = () => {
data: users = [],
isFetching: usersIsFetching,
error: usersError,
- } = useListUsersQuery({ tenantDomain })
+ } = useGenericGetRequestQuery({
+ path: '/api/ListGraphRequest',
+ params: {
+ Endpoint: 'users',
+ TenantFilter: tenantDomain,
+ $filter: 'assignedLicenses/$count ne 0 and accountEnabled eq true',
+ $count: true,
+ },
+ })
useEffect(() => {
if (!userId || !tenantDomain) {
@@ -259,7 +274,7 @@ const MailboxPermissions = () => {
multi={true}
label="Remove Full Access"
disabled={formDisabled}
- values={users?.map((user) => ({
+ values={users?.Results?.map((user) => ({
value: user.mail,
name: `${user.displayName} - ${user.mail} `,
}))}
@@ -273,7 +288,7 @@ const MailboxPermissions = () => {
multi={true}
label="Add Full Access - Automapping Enabled"
disabled={formDisabled}
- values={users?.map((user) => ({
+ values={users?.Results?.map((user) => ({
value: user.mail,
name: `${user.displayName} - ${user.mail} `,
}))}
@@ -287,7 +302,7 @@ const MailboxPermissions = () => {
multi={true}
label="Add Full Access - Automapping Disabled"
disabled={formDisabled}
- values={users?.map((user) => ({
+ values={users?.Results?.map((user) => ({
value: user.mail,
name: `${user.displayName} - ${user.mail} `,
}))}
@@ -301,7 +316,7 @@ const MailboxPermissions = () => {
multi={true}
label="Add Send-as permissions"
disabled={formDisabled}
- values={users?.map((user) => ({
+ values={users?.Results?.map((user) => ({
value: user.mail,
name: `${user.displayName} - ${user.mail} `,
}))}
@@ -315,7 +330,7 @@ const MailboxPermissions = () => {
multi={true}
label="Remove Send-as permissions"
disabled={formDisabled}
- values={users?.map((user) => ({
+ values={users?.Results?.map((user) => ({
value: user.mail,
name: `${user.displayName} - ${user.mail} `,
}))}
@@ -329,7 +344,7 @@ const MailboxPermissions = () => {
multi={true}
label="Add Send On Behalf permissions"
disabled={formDisabled}
- values={users?.map((user) => ({
+ values={users?.Results?.map((user) => ({
value: user.mail,
name: `${user.displayName} - ${user.mail} `,
}))}
@@ -343,7 +358,7 @@ const MailboxPermissions = () => {
multi={true}
label="Remove Send On Behalf permissions"
disabled={formDisabled}
- values={users?.map((user) => ({
+ values={users?.Results?.map((user) => ({
value: user.mail,
name: `${user.displayName} - ${user.mail} `,
}))}
@@ -396,16 +411,22 @@ const CalendarPermissions = () => {
const [queryError, setQueryError] = useState(false)
const {
- data: user = {},
+ data: user = [],
isFetching: userIsFetching,
error: userError,
- } = useListCalendarPermissionsQuery({ tenantDomain, userId })
+ } = useGenericGetRequestQuery({
+ path: '/api/ListCalendarPermissions',
+ params: { TenantFilter: tenantDomain, UserId: userId },
+ })
const {
data: users = [],
isFetching: usersIsFetching,
error: usersError,
- } = useListMailboxesQuery({ tenantDomain })
+ } = useGenericGetRequestQuery({
+ path: '/api/ListMailboxes',
+ params: { TenantFilter: tenantDomain, SkipLicense: true },
+ })
useEffect(() => {
if (!userId || !tenantDomain) {
@@ -444,21 +465,18 @@ const CalendarPermissions = () => {
return (
<>
+ {queryError && (
+
+
+
+ {/* @todo add more descriptive help message here */}
+ Failed to load user
+
+
+
+ )}
{!queryError && (
<>
- {postResults.isSuccess && (
- {postResults.data?.Results}
- )}
- {queryError && (
-
-
-
- {/* @todo add more descriptive help message here */}
- Failed to load user
-
-
-
- )}
{userIsFetching && }
@@ -500,10 +518,21 @@ const CalendarPermissions = () => {
{ value: 'Contributor', name: 'Contributor' },
{ value: 'Editor', name: 'Editor' },
{ value: 'Owner', name: 'Owner' },
- { value: 'NonEditingAuthor', name: 'Non Editing Author' },
- { value: 'PublishingAuthor', name: 'Publishing Author' },
- { value: 'PublishingEditor', name: 'Publishing Editor' },
+ {
+ value: 'NonEditingAuthor',
+ name: 'Non Editing Author',
+ },
+ {
+ value: 'PublishingAuthor',
+ name: 'Publishing Author',
+ },
+ {
+ value: 'PublishingEditor',
+ name: 'Publishing Editor',
+ },
{ value: 'Reviewer', name: 'Reviewer' },
+ { value: 'LimitedDetails', name: 'Limited Details' },
+ { value: 'AvailabilityOnly', name: 'Availability Only' },
]}
placeholder="Select a permission level"
name="Permissions"
@@ -563,10 +592,18 @@ const MailboxForwarding = () => {
data: users = [],
isFetching: usersIsFetching,
error: usersError,
- } = useListUsersQuery({ tenantDomain })
-
+ } = useGenericGetRequestQuery({
+ path: '/api/ListGraphRequest',
+ params: {
+ Endpoint: 'users',
+ TenantFilter: tenantDomain,
+ $filter: 'assignedLicenses/$count ne 0 and accountEnabled eq true',
+ $count: true,
+ },
+ })
useEffect(() => {
if (postResults.isSuccess) {
+ // @TODO do something here?
}
if (!userId || !tenantDomain) {
ModalService.open({
@@ -594,23 +631,6 @@ const MailboxForwarding = () => {
...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 (
@@ -655,7 +675,7 @@ const MailboxForwarding = () => {
({
+ values={users?.Results?.map((user) => ({
value: user.mail,
name: `${user.displayName} - ${user.mail} `,
}))}
@@ -799,10 +819,18 @@ const OutOfOffice = () => {
data: users = [],
isFetching: usersIsFetching,
error: usersError,
- } = useListUsersQuery({ tenantDomain })
-
+ } = useGenericGetRequestQuery({
+ path: '/api/ListGraphRequest',
+ params: {
+ Endpoint: 'users',
+ TenantFilter: tenantDomain,
+ $filter: 'assignedLicenses/$count ne 0 and accountEnabled eq true',
+ $count: true,
+ },
+ })
useEffect(() => {
if (postResults.isSuccess) {
+ // @TODO do something here?
}
if (!userId || !tenantDomain) {
ModalService.open({
@@ -819,8 +847,8 @@ const OutOfOffice = () => {
user: userId,
tenantFilter: tenantDomain,
AutoReplyState: values.AutoReplyState ? 'Scheduled' : 'Disabled',
- StartTime: startDate.toLocaleString(),
- EndTime: endDate.toLocaleString(),
+ StartTime: startDate.toUTCString(),
+ EndTime: endDate.toUTCString(),
InternalMessage: values.InternalMessage ? values.InternalMessage : '',
ExternalMessage: values.ExternalMessage ? values.ExternalMessage : '',
}
@@ -927,11 +955,7 @@ const OutOfOffice = () => {
{postResults.isSuccess && (
-
- {postResults.data.Results.map((result, idx) => (
- {result}
- ))}
-
+ {postResults.data?.Results}
)}
)
diff --git a/src/views/email-exchange/administration/MailboxRuleList.js b/src/views/email-exchange/administration/MailboxRuleList.jsx
similarity index 100%
rename from src/views/email-exchange/administration/MailboxRuleList.js
rename to src/views/email-exchange/administration/MailboxRuleList.jsx
diff --git a/src/views/email-exchange/administration/MailboxesList.js b/src/views/email-exchange/administration/MailboxesList.jsx
similarity index 98%
rename from src/views/email-exchange/administration/MailboxesList.js
rename to src/views/email-exchange/administration/MailboxesList.jsx
index cc4015b9708e..71bf2ba141b8 100644
--- a/src/views/email-exchange/administration/MailboxesList.js
+++ b/src/views/email-exchange/administration/MailboxesList.jsx
@@ -8,6 +8,7 @@ import { Link } from 'react-router-dom'
import { CippActionsOffcanvas } from 'src/components/utilities'
import { TitleButton } from 'src/components/buttons'
import { CellTip } from 'src/components/tables'
+import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat'
const MailboxList = () => {
const tenant = useSelector((state) => state.app.currentTenant)
@@ -17,7 +18,7 @@ const MailboxList = () => {
return (
<>
@@ -219,10 +220,10 @@ const MailboxList = () => {
},
{
name: 'Additional Email Addresses',
- selector: (row) => 'Click to Expand',
+ selector: (row) => row.AdditionalEmailAddresses,
exportSelector: 'AdditionalEmailAddresses',
sortable: true,
- omit: true,
+ cell: cellGenericFormatter(),
},
{
name: 'Actions',
diff --git a/src/views/email-exchange/administration/QuarantineList.js b/src/views/email-exchange/administration/QuarantineList.jsx
similarity index 100%
rename from src/views/email-exchange/administration/QuarantineList.js
rename to src/views/email-exchange/administration/QuarantineList.jsx
diff --git a/src/views/email-exchange/administration/ViewMobileDevices.js b/src/views/email-exchange/administration/ViewMobileDevices.jsx
similarity index 100%
rename from src/views/email-exchange/administration/ViewMobileDevices.js
rename to src/views/email-exchange/administration/ViewMobileDevices.jsx
diff --git a/src/views/email-exchange/connectors/AddConnectorTemplate.js b/src/views/email-exchange/connectors/AddConnectorTemplate.jsx
similarity index 100%
rename from src/views/email-exchange/connectors/AddConnectorTemplate.js
rename to src/views/email-exchange/connectors/AddConnectorTemplate.jsx
diff --git a/src/views/email-exchange/connectors/ConnectorList.js b/src/views/email-exchange/connectors/ConnectorList.jsx
similarity index 96%
rename from src/views/email-exchange/connectors/ConnectorList.js
rename to src/views/email-exchange/connectors/ConnectorList.jsx
index 2b818a2734d5..e4e887adbd71 100644
--- a/src/views/email-exchange/connectors/ConnectorList.js
+++ b/src/views/email-exchange/connectors/ConnectorList.jsx
@@ -143,7 +143,10 @@ const ConnectorList = () => {
title="Connector List"
titleButton={
<>
-
+
>
}
tenantSelector={true}
diff --git a/src/views/email-exchange/connectors/DeployConnector.js b/src/views/email-exchange/connectors/DeployConnector.jsx
similarity index 98%
rename from src/views/email-exchange/connectors/DeployConnector.js
rename to src/views/email-exchange/connectors/DeployConnector.jsx
index fee824106b2b..16ebec88e10a 100644
--- a/src/views/email-exchange/connectors/DeployConnector.js
+++ b/src/views/email-exchange/connectors/DeployConnector.jsx
@@ -64,6 +64,11 @@ const DeployConnectorTemplate = () => {
)
+ WhenFieldChanges.propTypes = {
+ field: PropTypes.node,
+ set: PropTypes.string,
+ }
+
const formValues = {
TemplateType: 'Admin',
}
@@ -84,6 +89,7 @@ const DeployConnectorTemplate = () => {
+ {/* eslint-disable react/prop-types */}
{(props) => (
{
if (tenant.defaultDomainName !== 'AllTenants') {
setTenantColumn(true)
}
- }, [tenantColumnSet])
+ }, [tenant.defaultDomainName, tenantColumnSet])
return (
row['UserPrincipalName'],
- name: 'User Prinicipal Name',
- sortable: true,
- cell: (row) => CellTip(row['UserPrincipalName']),
- exportSelector: 'UserPrincipalName',
- minWidth: '200px',
- },
- {
- selector: (row) => row['displayName'],
- name: 'Display Name',
- sortable: true,
- cell: (row) => CellTip(row['displayName']),
- exportSelector: 'displayName',
- minWidth: '200px',
- },
- {
- selector: (row) => row['givenName'],
- name: 'First Name',
- sortable: true,
- cell: (row) => CellTip(row['givenName']),
- exportSelector: 'givenName',
- minWidth: '200px',
- },
- {
- selector: (row) => row['surname'],
- name: 'Surname',
- sortable: true,
- cell: (row) => CellTip(row['surname']),
- exportSelector: 'surname',
- minWidth: '200px',
- },
- {
- selector: (row) => row['accountEnabled'],
- name: 'Account Enabled',
- sortable: true,
- cell: (row) => CellTip(row['accountEnabled']),
- exportSelector: 'accountEnabled',
- },
-]
-
-const SharedMailboxEnabledAccount = () => {
- const tenant = useSelector((state) => state.app.currentTenant)
-
- return (
-
- )
-}
-
-export default SharedMailboxEnabledAccount
diff --git a/src/views/email-exchange/reports/SharedMailboxEnabledAccount.jsx b/src/views/email-exchange/reports/SharedMailboxEnabledAccount.jsx
new file mode 100644
index 000000000000..b290c2ea9f3f
--- /dev/null
+++ b/src/views/email-exchange/reports/SharedMailboxEnabledAccount.jsx
@@ -0,0 +1,103 @@
+import React from 'react'
+import { CButton } from '@coreui/react'
+import { faBan } from '@fortawesome/free-solid-svg-icons'
+import { useSelector } from 'react-redux'
+import { CellTip, cellBooleanFormatter } from 'src/components/tables'
+import { CippPageList } from 'src/components/layout'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { ModalService } from 'src/components/utilities'
+import { useLazyGenericGetRequestQuery } from 'src/store/api/app'
+
+const DisableSharedMailbox = (userId) => {
+ const tenant = useSelector((state) => state.app.currentTenant)
+ const [genericGetRequest, getResults] = useLazyGenericGetRequestQuery()
+ const handleModal = (modalMessage, modalUrl) => {
+ ModalService.confirm({
+ body: (
+
+ ),
+ title: 'Confirm',
+ onConfirm: () => genericGetRequest({ path: modalUrl }),
+ })
+ }
+
+ return (
+ <>
+ {
+ ModalService.confirm(
+ handleModal(
+ 'Are you sure you want to block this user from signing in?',
+ `/api/ExecDisableUser?TenantFilter=${tenant?.defaultDomainName}&ID=${userId}`,
+ ),
+ )
+ }}
+ >
+
+
+ >
+ )
+}
+
+const columns = [
+ {
+ selector: (row) => row['UserPrincipalName'],
+ name: 'User Prinicipal Name',
+ sortable: true,
+ cell: (row) => CellTip(row['UserPrincipalName']),
+ exportSelector: 'UserPrincipalName',
+ minWidth: '200px',
+ },
+ {
+ selector: (row) => row['displayName'],
+ name: 'Display Name',
+ sortable: true,
+ cell: (row) => CellTip(row['displayName']),
+ exportSelector: 'displayName',
+ minWidth: '200px',
+ },
+ {
+ name: 'Account Enabled',
+ selector: (row) => row['accountEnabled'],
+ cell: cellBooleanFormatter({ colourless: true }),
+ sortable: true,
+ exportSelector: 'accountEnabled',
+ minWidth: '50px',
+ },
+ {
+ name: 'AD Synced',
+ selector: (row) => row['onPremisesSyncEnabled'],
+ cell: cellBooleanFormatter({ colourless: true }),
+ sortable: true,
+ exportSelector: 'onPremisesSyncEnabled',
+ minWidth: '75px',
+ },
+ {
+ name: 'Block sign-in',
+ cell: (row) => DisableSharedMailbox(row['id']),
+ minWidth: '100px',
+ },
+]
+
+const SharedMailboxEnabledAccount = () => {
+ const tenant = useSelector((state) => state.app.currentTenant)
+
+ return (
+
+ )
+}
+
+export default SharedMailboxEnabledAccount
diff --git a/src/views/email-exchange/spamfilter/AddSpamfilterTemplate.js b/src/views/email-exchange/spamfilter/AddSpamfilterTemplate.jsx
similarity index 100%
rename from src/views/email-exchange/spamfilter/AddSpamfilterTemplate.js
rename to src/views/email-exchange/spamfilter/AddSpamfilterTemplate.jsx
diff --git a/src/views/email-exchange/spamfilter/DeploySpamfilter.js b/src/views/email-exchange/spamfilter/DeploySpamfilter.jsx
similarity index 97%
rename from src/views/email-exchange/spamfilter/DeploySpamfilter.js
rename to src/views/email-exchange/spamfilter/DeploySpamfilter.jsx
index f798ebf48003..4a8c7e0e21b0 100644
--- a/src/views/email-exchange/spamfilter/DeploySpamfilter.js
+++ b/src/views/email-exchange/spamfilter/DeploySpamfilter.jsx
@@ -64,6 +64,11 @@ const SpamFilterAdd = () => {
)
+ WhenFieldChanges.propTypes = {
+ field: PropTypes.node,
+ set: PropTypes.string,
+ }
+
const formValues = {
TemplateType: 'Admin',
}
@@ -158,6 +163,7 @@ const SpamFilterAdd = () => {
{!postResults.isSuccess && (
+ {/* eslint-disable react/prop-types */}
{(props) => {
return (
<>
diff --git a/src/views/email-exchange/spamfilter/ListSpamfilterTemplates.js b/src/views/email-exchange/spamfilter/ListSpamfilterTemplates.jsx
similarity index 96%
rename from src/views/email-exchange/spamfilter/ListSpamfilterTemplates.js
rename to src/views/email-exchange/spamfilter/ListSpamfilterTemplates.jsx
index 076a7879c423..eb5bad83bbc3 100644
--- a/src/views/email-exchange/spamfilter/ListSpamfilterTemplates.js
+++ b/src/views/email-exchange/spamfilter/ListSpamfilterTemplates.jsx
@@ -1,6 +1,5 @@
import React, { useState } from 'react'
import { useSelector } from 'react-redux'
-import { CippCodeBlock, CippOffcanvas } from 'src/components/utilities'
import { CellTip } from 'src/components/tables'
import { CButton, CCallout, CSpinner } from '@coreui/react'
import { faEye, faTrash } from '@fortawesome/free-solid-svg-icons'
@@ -8,7 +7,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { useLazyGenericGetRequestQuery } from 'src/store/api/app'
import { CippPageList } from 'src/components/layout'
import { ModalService } from 'src/components/utilities'
-import { TitleButton } from 'src/components/buttons'
import CippCodeOffCanvas from 'src/components/utilities/CippCodeOffcanvas'
const SpamFilterListTemplates = () => {
diff --git a/src/views/email-exchange/spamfilter/Spamfilter.js b/src/views/email-exchange/spamfilter/Spamfilter.jsx
similarity index 95%
rename from src/views/email-exchange/spamfilter/Spamfilter.js
rename to src/views/email-exchange/spamfilter/Spamfilter.jsx
index f250ea4ef58a..75757fe835c6 100644
--- a/src/views/email-exchange/spamfilter/Spamfilter.js
+++ b/src/views/email-exchange/spamfilter/Spamfilter.jsx
@@ -6,6 +6,7 @@ import { useSelector } from 'react-redux'
import { CippPageList } from 'src/components/layout'
import { CippActionsOffcanvas } from 'src/components/utilities'
import { cellBooleanFormatter, cellDateFormatter, CellTip } from 'src/components/tables'
+import { TitleButton } from 'src/components/buttons'
const Offcanvas = (row, rowIndex, formatExtraData) => {
const tenant = useSelector((state) => state.app.currentTenant)
@@ -172,6 +173,14 @@ const SpamFilterList = () => {
return (
+
+ >
+ }
tenantSelector={true}
datatable={{
reportName: `${tenant?.defaultDomainName}-Spamfilter-list`,
diff --git a/src/views/email-exchange/tools/MailboxRestoreWizard.js b/src/views/email-exchange/tools/MailboxRestoreWizard.jsx
similarity index 93%
rename from src/views/email-exchange/tools/MailboxRestoreWizard.js
rename to src/views/email-exchange/tools/MailboxRestoreWizard.jsx
index 2e8acddc59f7..490c7d54f98d 100644
--- a/src/views/email-exchange/tools/MailboxRestoreWizard.js
+++ b/src/views/email-exchange/tools/MailboxRestoreWizard.jsx
@@ -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'
@@ -38,13 +38,18 @@ 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 = [],
@@ -52,7 +57,7 @@ const MailboxRestoreWizard = () => {
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()
@@ -104,6 +109,7 @@ const MailboxRestoreWizard = () => {
}))}
placeholder={!sMailboxesIsFetching ? 'Select mailbox' : 'Loading...'}
name="SourceMailbox"
+ isLoading={sMailboxesIsFetching}
/>
{sMailboxError && Failed to load source mailboxes }
@@ -118,18 +124,20 @@ const MailboxRestoreWizard = () => {
({
value: mbx.ExchangeGuid,
name: `${mbx.displayName} <${mbx.UPN}>`,
}))}
- placeholder={!tMailboxesIsFetching ? 'Select mailbox' : 'Loading...'}
- name="TargetMailbox"
+ retainInput={true}
+ onInputChange={setAnrFilter}
+ isLoading={tMailboxesIsFetching}
/>
{sMailboxError && Failed to load source mailboxes }
-
+
Step 3
Enter Restore Request Options
@@ -180,6 +188,7 @@ const MailboxRestoreWizard = () => {
)}
{!postResults.isSuccess && (
+ {/* eslint-disable react/prop-types */}
{(props) => (
<>
diff --git a/src/views/email-exchange/tools/MailboxRestores.js b/src/views/email-exchange/tools/MailboxRestores.jsx
similarity index 80%
rename from src/views/email-exchange/tools/MailboxRestores.js
rename to src/views/email-exchange/tools/MailboxRestores.jsx
index 05459fa1e08d..3a12702dd2ce 100644
--- a/src/views/email-exchange/tools/MailboxRestores.js
+++ b/src/views/email-exchange/tools/MailboxRestores.jsx
@@ -1,24 +1,12 @@
-import { CSpinner, CButton } from '@coreui/react'
-import {
- faEllipsisV,
- faTrashAlt,
- faExclamationTriangle,
- faCheck,
-} from '@fortawesome/free-solid-svg-icons'
+import { CButton } from '@coreui/react'
+import { faEllipsisV } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import React, { useState } from 'react'
import { useSelector } from 'react-redux'
import { CippPageList } from 'src/components/layout'
-import {
- CellDelegatedPrivilege,
- cellDateFormatter,
- cellNullTextFormatter,
-} from 'src/components/tables'
+import { cellDateFormatter } from 'src/components/tables'
import { CippActionsOffcanvas } from 'src/components/utilities'
-import GDAPRoles from 'src/data/GDAPRoles'
import { useLazyGenericGetRequestQuery } from 'src/store/api/app'
-import { ModalService } from 'src/components/utilities'
-import { constants } from 'buffer'
import Skeleton from 'react-loading-skeleton'
import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat'
@@ -93,6 +81,27 @@ const Actions = (row, rowIndex, formatExtraData) => {
label: 'Show Report',
color: 'info',
},
+ {
+ label: 'Resume Restore Request',
+ color: 'info',
+ modal: true,
+ modalUrl: `/api/ExecMailboxRestore?TenantFilter=${tenant.defaultDomainName}&Identity=${row.Identity}&Action=Resume`,
+ modalMessage: 'Are you sure you want to resume this restore request?',
+ },
+ {
+ label: 'Suspend Restore Request',
+ color: 'warning',
+ modal: true,
+ modalUrl: `/api/ExecMailboxRestore?TenantFilter=${tenant.defaultDomainName}&Identity=${row.Identity}&Action=Suspend`,
+ modalMessage: 'Are you sure you want to suspend this restore request?',
+ },
+ {
+ label: 'Remove Restore Request',
+ color: 'danger',
+ modal: true,
+ modalUrl: `/api/ExecMailboxRestore?TenantFilter=${tenant.defaultDomainName}&Identity=${row.Identity}&Action=Remove`,
+ modalMessage: 'Are you sure you want to remove this restore request?',
+ },
]}
placement="end"
visible={ocVisible}
diff --git a/src/views/email-exchange/transport/AddTransportTemplate.js b/src/views/email-exchange/transport/AddTransportTemplate.jsx
similarity index 100%
rename from src/views/email-exchange/transport/AddTransportTemplate.js
rename to src/views/email-exchange/transport/AddTransportTemplate.jsx
diff --git a/src/views/email-exchange/transport/DeployTransport.js b/src/views/email-exchange/transport/DeployTransport.jsx
similarity index 98%
rename from src/views/email-exchange/transport/DeployTransport.js
rename to src/views/email-exchange/transport/DeployTransport.jsx
index e53991072fa2..4ba34de996b4 100644
--- a/src/views/email-exchange/transport/DeployTransport.js
+++ b/src/views/email-exchange/transport/DeployTransport.jsx
@@ -41,7 +41,6 @@ const AddPolicy = () => {
values.TemplateType = values.Type
genericPostRequest({ path: '/api/AddTransportRule', values: values })
}
- //* eslint-disable react/prop-types */
const WhenFieldChanges = ({ field, set }) => (
{(
@@ -65,6 +64,11 @@ const AddPolicy = () => {
)
+ WhenFieldChanges.propTypes = {
+ field: PropTypes.node,
+ set: PropTypes.string,
+ }
+
const formValues = {
TemplateType: 'Admin',
}
@@ -85,6 +89,7 @@ const AddPolicy = () => {
+ {/* eslint-disable react/prop-types */}
{(props) => (
{
tenantSelector={true}
titleButton={
<>
-
+
>
}
datatable={{
diff --git a/src/views/endpoint/applications/ApplicationsAddChocoApp.js b/src/views/endpoint/applications/ApplicationsAddChocoApp.jsx
similarity index 98%
rename from src/views/endpoint/applications/ApplicationsAddChocoApp.js
rename to src/views/endpoint/applications/ApplicationsAddChocoApp.jsx
index 6f518d6f5d07..7d4ef791b391 100644
--- a/src/views/endpoint/applications/ApplicationsAddChocoApp.js
+++ b/src/views/endpoint/applications/ApplicationsAddChocoApp.jsx
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { useRef } from 'react'
import {
CCol,
CRow,
@@ -27,7 +27,6 @@ import {
} from 'src/components/forms'
import { useLazyGenericPostRequestQuery } from 'src/store/api/app'
import { OnChange } from 'react-final-form-listeners'
-import { useRef } from 'react'
const Error = ({ name }) => (
{
)}
)
+
+ WhenFieldChanges.propTypes = {
+ field: PropTypes.node,
+ set: PropTypes.string,
+ }
+
return (
{
{!postResults.isSuccess && (
+ {/* eslint-disable react/prop-types */}
{(props) => {
return (
<>
diff --git a/src/views/endpoint/applications/ApplicationsAddOffice.js b/src/views/endpoint/applications/ApplicationsAddOffice.jsx
similarity index 99%
rename from src/views/endpoint/applications/ApplicationsAddOffice.js
rename to src/views/endpoint/applications/ApplicationsAddOffice.jsx
index 5fcd8c673b07..a3cb1890bbff 100644
--- a/src/views/endpoint/applications/ApplicationsAddOffice.js
+++ b/src/views/endpoint/applications/ApplicationsAddOffice.jsx
@@ -197,6 +197,7 @@ const AddOffice = () => {
{!postResults.isSuccess && (
+ {/* eslint-disable react/prop-types */}
{(props) => {
return (
<>
diff --git a/src/views/endpoint/applications/ApplicationsAddRMM.js b/src/views/endpoint/applications/ApplicationsAddRMM.jsx
similarity index 99%
rename from src/views/endpoint/applications/ApplicationsAddRMM.js
rename to src/views/endpoint/applications/ApplicationsAddRMM.jsx
index da43bbca5a29..a6583e00fd4c 100644
--- a/src/views/endpoint/applications/ApplicationsAddRMM.js
+++ b/src/views/endpoint/applications/ApplicationsAddRMM.jsx
@@ -129,6 +129,7 @@ const AddRMM = () => {
+ {/* eslint-disable react/prop-types */}
{(props) => {
return (
<>
diff --git a/src/views/endpoint/applications/ApplicationsAddWinGet.js b/src/views/endpoint/applications/ApplicationsAddWinGet.jsx
similarity index 98%
rename from src/views/endpoint/applications/ApplicationsAddWinGet.js
rename to src/views/endpoint/applications/ApplicationsAddWinGet.jsx
index 72a1dc7937f4..dda13eecf7c6 100644
--- a/src/views/endpoint/applications/ApplicationsAddWinGet.js
+++ b/src/views/endpoint/applications/ApplicationsAddWinGet.jsx
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { useRef } from 'react'
import {
CCol,
CRow,
@@ -23,10 +23,8 @@ import {
RFFCFormInput,
RFFCFormRadio,
RFFCFormSelect,
- RFFCFormSwitch,
} from 'src/components/forms'
import { useLazyGenericPostRequestQuery } from 'src/store/api/app'
-import { useRef } from 'react'
import { OnChange } from 'react-final-form-listeners'
const Error = ({ name }) => (
@@ -103,6 +101,11 @@ const AddWinGet = () => {
)
+ WhenFieldChanges.propTypes = {
+ field: PropTypes.node,
+ set: PropTypes.string,
+ }
+
return (
{
{!postResults.isSuccess && (
+ {/* eslint-disable react/prop-types */}
{(props) => {
return (
<>
diff --git a/src/views/endpoint/applications/ApplicationsList.js b/src/views/endpoint/applications/ApplicationsList.jsx
similarity index 85%
rename from src/views/endpoint/applications/ApplicationsList.js
rename to src/views/endpoint/applications/ApplicationsList.jsx
index 547f036da607..6df4a06d221a 100644
--- a/src/views/endpoint/applications/ApplicationsList.js
+++ b/src/views/endpoint/applications/ApplicationsList.jsx
@@ -6,6 +6,7 @@ import { CippPageList } from 'src/components/layout'
import { faEllipsisV, faGlobeEurope, faPager, faUser } from '@fortawesome/free-solid-svg-icons'
import { CippActionsOffcanvas } from 'src/components/utilities'
import { CellTip } from 'src/components/tables'
+import { TitleButton } from 'src/components/buttons'
const Offcanvas = (row, rowIndex, formatExtraData) => {
const tenant = useSelector((state) => state.app.currentTenant)
@@ -125,9 +126,39 @@ const columns = [
const ApplicationsList = () => {
const tenant = useSelector((state) => state.app.currentTenant)
+ const titleButtons = (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+
return (
{
+ {/* eslint-disable react/prop-types */}
{(props) => {
return (
<>
diff --git a/src/views/endpoint/autopilot/AutopilotAddProfile.js b/src/views/endpoint/autopilot/AutopilotAddProfile.jsx
similarity index 99%
rename from src/views/endpoint/autopilot/AutopilotAddProfile.js
rename to src/views/endpoint/autopilot/AutopilotAddProfile.jsx
index 4247ea12a19c..62c62c8b4cf4 100644
--- a/src/views/endpoint/autopilot/AutopilotAddProfile.js
+++ b/src/views/endpoint/autopilot/AutopilotAddProfile.jsx
@@ -168,6 +168,7 @@ const ApplyStandard = () => {
{!postResults.isSuccess && (
+ {/* eslint-disable react/prop-types */}
{(props) => {
return (
<>
diff --git a/src/views/endpoint/autopilot/AutopilotAddStatusPage.js b/src/views/endpoint/autopilot/AutopilotAddStatusPage.jsx
similarity index 99%
rename from src/views/endpoint/autopilot/AutopilotAddStatusPage.js
rename to src/views/endpoint/autopilot/AutopilotAddStatusPage.jsx
index 516ee21de8be..85f83fd385a3 100644
--- a/src/views/endpoint/autopilot/AutopilotAddStatusPage.js
+++ b/src/views/endpoint/autopilot/AutopilotAddStatusPage.jsx
@@ -140,6 +140,7 @@ const ApplyStandard = () => {
{!postResults.isSuccess && (
+ {/* eslint-disable react/prop-types */}
{(props) => {
return (
<>
diff --git a/src/views/endpoint/autopilot/AutopilotListDevices.js b/src/views/endpoint/autopilot/AutopilotListDevices.js
deleted file mode 100644
index da2f8698be7d..000000000000
--- a/src/views/endpoint/autopilot/AutopilotListDevices.js
+++ /dev/null
@@ -1,115 +0,0 @@
-import React from 'react'
-import { useSelector } from 'react-redux'
-import { CButton, CCallout, CSpinner } from '@coreui/react'
-import { faTrash } from '@fortawesome/free-solid-svg-icons'
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
-import { CippPageList } from 'src/components/layout'
-import { ModalService } from 'src/components/utilities'
-import { useLazyGenericGetRequestQuery } from 'src/store/api/app'
-import { CellTip } from 'src/components/tables'
-
-const AutopilotListDevices = () => {
- const tenant = useSelector((state) => state.app.currentTenant)
- const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery()
- const Actions = (row, index, column) => {
- const handleDeleteAPDevice = (apiurl, message) => {
- ModalService.confirm({
- title: 'Confirm',
- body: {message}
,
- onConfirm: () => ExecuteGetRequest({ path: apiurl }),
- confirmLabel: 'Continue',
- cancelLabel: 'Cancel',
- })
- }
- return (
-
- handleDeleteAPDevice(
- `api/RemoveAPDevice?ID=${row.id}&tenantFilter=${tenant.defaultDomainName}`,
- 'Do you want to delete the Autopilot Device?',
- )
- }
- >
-
-
- )
- }
-
- const columns = [
- {
- selector: (row) => row['displayName'],
- name: 'Display Name',
- sortable: true,
- cell: (row) => CellTip(row['displayName']),
- exportSelector: 'displayName',
- },
- {
- selector: (row) => row['serialNumber'],
- name: 'Serial',
- sortable: true,
- cell: (row) => CellTip(row['serialNumber']),
- exportSelector: 'serialNumber',
- },
- {
- selector: (row) => row['model'],
- name: 'Model',
- sortable: true,
- cell: (row) => CellTip(row['model']),
- exportSelector: 'model',
- },
- {
- selector: (row) => row['manufacturer'],
- name: 'Manufacturer',
- sortable: true,
- cell: (row) => CellTip(row['manufacturer']),
- exportSelector: 'manufacturer',
- },
- {
- selector: (row) => row['groupTag'],
- name: 'Group Tag',
- sortable: true,
- cell: (row) => CellTip(row['groupTag']),
- exportSelector: 'groupTag',
- },
- {
- selector: (row) => row['enrollmentState'],
- name: 'Enrollment',
- sortable: true,
- cell: (row) => CellTip(row['enrollmentState']),
- exportSelector: 'enrollmentState',
- },
- {
- name: (row) => row['Actions'],
- cell: Actions,
- },
- ]
-
- return (
- <>
- {getResults.isFetching && (
-
- Loading
-
- )}
- {getResults.isSuccess && {getResults.data?.Results} }
- {getResults.isError && (
- Could not connect to API: {getResults.error.message}
- )}
-
- >
- )
-}
-
-export default AutopilotListDevices
diff --git a/src/views/endpoint/autopilot/AutopilotListDevices.jsx b/src/views/endpoint/autopilot/AutopilotListDevices.jsx
new file mode 100644
index 000000000000..ef74e81d42fc
--- /dev/null
+++ b/src/views/endpoint/autopilot/AutopilotListDevices.jsx
@@ -0,0 +1,173 @@
+import React, { useState } from 'react'
+import { useSelector } from 'react-redux'
+import { CButton, CCallout, CSpinner } from '@coreui/react'
+import {
+ faArrowCircleDown,
+ faEllipsisV,
+ faSyncAlt,
+ faTrash,
+} from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { CippPageList } from 'src/components/layout'
+import { CippActionsOffcanvas, ModalService } from 'src/components/utilities'
+import { useLazyGenericGetRequestQuery } from 'src/store/api/app'
+import { CellTip } from 'src/components/tables'
+import { TitleButton } from 'src/components/buttons'
+
+const AutopilotListDevices = () => {
+ const tenant = useSelector((state) => state.app.currentTenant)
+ const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery()
+ const [ocVisible, setOCVisible] = useState(false)
+
+ const Actions = (row, index, column) => {
+ return (
+ <>
+ setOCVisible(true)}>
+
+
+ setOCVisible(false)}
+ />
+ >
+ )
+ }
+
+ const columns = [
+ {
+ selector: (row) => row['displayName'],
+ name: 'Display Name',
+ sortable: true,
+ cell: (row) => CellTip(row['displayName']),
+ exportSelector: 'displayName',
+ },
+ {
+ selector: (row) => row['serialNumber'],
+ name: 'Serial',
+ sortable: true,
+ cell: (row) => CellTip(row['serialNumber']),
+ exportSelector: 'serialNumber',
+ },
+ {
+ selector: (row) => row['model'],
+ name: 'Model',
+ sortable: true,
+ cell: (row) => CellTip(row['model']),
+ exportSelector: 'model',
+ },
+ {
+ selector: (row) => row['manufacturer'],
+ name: 'Manufacturer',
+ sortable: true,
+ cell: (row) => CellTip(row['manufacturer']),
+ exportSelector: 'manufacturer',
+ },
+ {
+ selector: (row) => row['groupTag'],
+ name: 'Group Tag',
+ sortable: true,
+ cell: (row) => CellTip(row['groupTag']),
+ exportSelector: 'groupTag',
+ },
+ {
+ selector: (row) => row['enrollmentState'],
+ name: 'Enrollment',
+ sortable: true,
+ cell: (row) => CellTip(row['enrollmentState']),
+ exportSelector: 'enrollmentState',
+ },
+ {
+ name: (row) => row['Actions'],
+ cell: Actions,
+ },
+ ]
+
+ return (
+ <>
+ {getResults.isFetching && (
+
+ Loading
+
+ )}
+ {getResults.isSuccess && {getResults.data?.Results} }
+ {getResults.isError && (
+ Could not connect to API: {getResults.error.message}
+ )}
+
+
+
+
+ ExecuteGetRequest({
+ path: `/api/ExecSyncAPDevices?tenantFilter=${tenant.defaultDomainName}`,
+ })
+ }
+ title="Sync Devices"
+ />
+
+
+ }
+ datatable={{
+ keyField: 'id',
+ reportName: `${tenant?.defaultDomainName}-AutopilotDevices-List`,
+ path: `/api/ListAPDevices`,
+ columns,
+ params: { TenantFilter: tenant?.defaultDomainName },
+ }}
+ />
+ >
+ )
+}
+
+export default AutopilotListDevices
diff --git a/src/views/endpoint/autopilot/AutopilotListProfiles.js b/src/views/endpoint/autopilot/AutopilotListProfiles.jsx
similarity index 89%
rename from src/views/endpoint/autopilot/AutopilotListProfiles.js
rename to src/views/endpoint/autopilot/AutopilotListProfiles.jsx
index a553dd63b825..659ef1a81d15 100644
--- a/src/views/endpoint/autopilot/AutopilotListProfiles.js
+++ b/src/views/endpoint/autopilot/AutopilotListProfiles.jsx
@@ -6,6 +6,7 @@ import { faEye } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { CippCodeBlock, CippOffcanvas } from 'src/components/utilities'
import { CippPageList } from 'src/components/layout'
+import { TitleButton } from 'src/components/buttons'
const Offcanvas = (row, rowIndex, formatExtraData) => {
const [visible, setVisible] = useState(false)
@@ -88,6 +89,14 @@ const AutopilotListProfiles = () => {
return (
+
+ >
+ }
tenantSelector={true}
datatable={{
reportName: `${tenant?.defaultDomainName}-AutopilotProfile-List`,
diff --git a/src/views/endpoint/autopilot/AutopilotListStatusPages.js b/src/views/endpoint/autopilot/AutopilotListStatusPages.jsx
similarity index 87%
rename from src/views/endpoint/autopilot/AutopilotListStatusPages.js
rename to src/views/endpoint/autopilot/AutopilotListStatusPages.jsx
index 04f450f2e205..352ecfee0800 100644
--- a/src/views/endpoint/autopilot/AutopilotListStatusPages.js
+++ b/src/views/endpoint/autopilot/AutopilotListStatusPages.jsx
@@ -2,6 +2,7 @@ import React from 'react'
import { useSelector } from 'react-redux'
import { CellTip, cellBooleanFormatter } from 'src/components/tables'
import { CippPageList } from 'src/components/layout'
+import { TitleButton } from 'src/components/buttons'
const columns = [
{
@@ -61,6 +62,14 @@ const AutopilotListESP = () => {
return (
+
+ >
+ }
tenantSelector={true}
datatable={{
reportName: `${tenant?.defaultDomainName}-AutopilotStatusPages-List`,
diff --git a/src/views/endpoint/intune/Devices.js b/src/views/endpoint/intune/Devices.jsx
similarity index 100%
rename from src/views/endpoint/intune/Devices.js
rename to src/views/endpoint/intune/Devices.jsx
diff --git a/src/views/endpoint/intune/MEMAddPolicy.js b/src/views/endpoint/intune/MEMAddPolicy.jsx
similarity index 98%
rename from src/views/endpoint/intune/MEMAddPolicy.js
rename to src/views/endpoint/intune/MEMAddPolicy.jsx
index a564ce3d36db..6c7efa88c689 100644
--- a/src/views/endpoint/intune/MEMAddPolicy.js
+++ b/src/views/endpoint/intune/MEMAddPolicy.jsx
@@ -79,6 +79,11 @@ const AddPolicy = () => {
)
+ WhenFieldChanges.propTypes = {
+ field: PropTypes.node,
+ set: PropTypes.string,
+ }
+
const formValues = {
TemplateType: 'Admin',
}
@@ -99,6 +104,7 @@ const AddPolicy = () => {
+ {/* eslint-disable react/prop-types */}
{(props) => (
{
const [ocVisible, setOCVisible] = useState(false)
@@ -13,13 +22,6 @@ const Actions = (row, rowIndex, formatExtraData) => {
const tenant = useSelector((state) => state.app.currentTenant)
return (
<>
-
-
-
-
-
setOCVisible(true)}>
@@ -40,6 +42,30 @@ const Actions = (row, rowIndex, formatExtraData) => {
modalUrl: `/api/AddIntuneTemplate?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&URLName=${row.URLName}`,
modalMessage: 'Are you sure you want to create a template based on this policy?',
},
+ {
+ icon: ,
+ label: ' Assign to All Users',
+ color: 'info',
+ modal: true,
+ modalUrl: `/api/ExecAssignPolicy?AssignTo=allLicensedUsers&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`,
+ modalMessage: `Are you sure you want to assign ${row.displayName} to all users?`,
+ },
+ {
+ icon: ,
+ label: ' Assign to All Devices',
+ color: 'info',
+ modal: true,
+ modalUrl: `/api/ExecAssignPolicy?AssignTo=AllDevices&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`,
+ modalMessage: `Are you sure you want to assign ${row.displayName} to all devices?`,
+ },
+ {
+ icon: ,
+ label: ' Assign Globally (All Users / All Devices)',
+ color: 'info',
+ modal: true,
+ modalUrl: `/api/ExecAssignPolicy?AssignTo=AllDevicesAndUsers&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`,
+ modalMessage: `Are you sure you want to assign ${row.displayName} to all users and devices?`,
+ },
{
label: 'Delete Policy',
color: 'danger',
@@ -95,6 +121,14 @@ const IntuneList = () => {
return (
+
+ >
+ }
tenantSelector={true}
datatable={{
path: '/api/ListIntunePolicy?type=ESP',
diff --git a/src/views/endpoint/intune/MEMListPolicyTemplates.js b/src/views/endpoint/intune/MEMListPolicyTemplates.jsx
similarity index 98%
rename from src/views/endpoint/intune/MEMListPolicyTemplates.js
rename to src/views/endpoint/intune/MEMListPolicyTemplates.jsx
index 851c6731bedd..4ca9452daeff 100644
--- a/src/views/endpoint/intune/MEMListPolicyTemplates.js
+++ b/src/views/endpoint/intune/MEMListPolicyTemplates.jsx
@@ -1,6 +1,5 @@
import React, { useState } from 'react'
import { useSelector } from 'react-redux'
-import { CippCodeBlock, CippOffcanvas } from 'src/components/utilities'
import { CellTip, CippDatatable } from 'src/components/tables'
import {
CCardBody,
diff --git a/src/views/home/Home.js b/src/views/home/Home.jsx
similarity index 80%
rename from src/views/home/Home.js
rename to src/views/home/Home.jsx
index 761be7b0d35d..e667c9fc0cbd 100644
--- a/src/views/home/Home.js
+++ b/src/views/home/Home.jsx
@@ -29,10 +29,11 @@ import { CellDelegatedPrivilege } from 'src/components/tables/CellDelegatedPrivi
import Portals from 'src/data/portals'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { Link } from 'react-router-dom'
+import { TableModalButton } from 'src/components/buttons'
const Home = () => {
const [visible, setVisible] = useState(false)
-
+ const [domainVisible, setDomainVisible] = useState(false)
const currentTenant = useSelector((state) => state.app.currentTenant)
const {
data: organization,
@@ -54,6 +55,15 @@ const Home = () => {
params: { tenantFilter: currentTenant.defaultDomainName },
})
+ const GlobalAdminList = useGenericGetRequestQuery({
+ path: '/api/ListGraphRequest',
+ params: {
+ tenantFilter: currentTenant.defaultDomainName,
+ Endpoint: "/directoryRoles(roleTemplateId='62e90394-69f5-4237-9190-012177145e10')/members",
+ $select: 'displayName,userPrincipalName,accountEnabled',
+ },
+ })
+
const {
data: sharepoint,
isLoading: isLoadingSPQuota,
@@ -106,6 +116,11 @@ const Home = () => {
link: `/identity/administration/users?customerId=${currentTenant.customerId}`,
icon: faUser,
},
+ {
+ label: 'List Mailboxes',
+ link: `/email/administration/mailboxes?customerId=${currentTenant.customerId}`,
+ icon: faMailBulk,
+ },
{
label: 'List Groups',
link: `/identity/administration/groups?customerId=${currentTenant.customerId}`,
@@ -133,10 +148,10 @@ const Home = () => {
(p) => p.displayName === 'AllTenants' || p.displayName === currentTenant.defaultDomainName,
)
.flatMap((tenant) => {
- return Object.keys(tenant.standards).map((standard) => {
+ return Object.keys(tenant.standards).map((standard, idx) => {
const standardDisplayname = allStandardsList.filter((p) => p.name.includes(standard))
return (
-
+
{standardDisplayname[0]?.label} ({tenant.displayName})
)
@@ -187,14 +202,20 @@ const Home = () => {
-
-
- {issuccessUserCounts && !isFetchingUserCount ? dashboard?.Gas : }
-
+ {GlobalAdminList.isSuccess ? (
+ <>
+
+ >
+ ) : (
+
+ )}
@@ -246,7 +267,7 @@ const Home = () => {
{!isLoadingOrg && !isFetchingOrg && organization?.onPremisesSyncEnabled ? (
<>
- Directory Sync:
+ Directory Sync:
{organization?.onPremisesLastSyncDateTime ? (
) : (
@@ -254,7 +275,7 @@ const Home = () => {
)}
- Password Sync:
+ Password Sync:
{organization?.onPremisesLastPasswordSyncDateTime ? (
) : (
@@ -271,9 +292,31 @@ const Home = () => {
Domain(s)
{(isLoadingOrg || isFetchingOrg) && }
- {!isFetchingOrg &&
- issuccessOrg &&
- organization?.verifiedDomains?.map((item) => {item.name} )}
+ <>
+ {!isFetchingOrg && issuccessOrg && (
+ <>
+ {organization.verifiedDomains?.slice(0, 5).map((item, idx) => (
+ {item.name}
+ ))}
+ {organization.verifiedDomains?.length > 5 && (
+ <>
+
+ {organization.verifiedDomains?.slice(5).map((item, idx) => (
+ {item.name}
+ ))}
+
+ setDomainVisible(!domainVisible)}
+ >
+ {domainVisible ? 'See less' : 'See more...'}
+
+ >
+ )}
+ >
+ )}
+ >
Capabilities
@@ -288,12 +331,12 @@ const Home = () => {
}
return plan
}, [])
- .map((plan) => (
- <>
- {plan == 'exchange' && Exchange }
- {plan == 'AADPremiumService' && AAD Premium }
- {plan == 'WindowsDefenderATP' && Windows Defender }
- >
+ .map((plan, idx) => (
+
+ {plan === 'exchange' &&
Exchange }
+ {plan === 'AADPremiumService' && AAD Premium }
+ {plan === 'WindowsDefenderATP' && Windows Defender }
+
))}
@@ -329,10 +372,10 @@ const Home = () => {
{(isLoadingPartners || isFetchingPartners) && }
{issuccessPartners &&
!isFetchingPartners &&
- partners?.Results.map((partner) => {
+ partners?.Results.map((partner, idx) => {
if (partner.TenantInfo) {
return (
-
+
{partner.TenantInfo.displayName} (
{partner.TenantInfo.defaultDomainName})
diff --git a/src/views/identity/administration/AddGroup.js b/src/views/identity/administration/AddGroup.jsx
similarity index 61%
rename from src/views/identity/administration/AddGroup.js
rename to src/views/identity/administration/AddGroup.jsx
index 540ddcfa4202..9faa8544bca7 100644
--- a/src/views/identity/administration/AddGroup.js
+++ b/src/views/identity/administration/AddGroup.jsx
@@ -19,10 +19,12 @@ import {
RFFCFormRadio,
RFFCFormSelect,
RFFCFormTextarea,
+ RFFSelectSearch,
} from 'src/components/forms'
import { CippPage } from 'src/components/layout/CippPage'
import { useLazyGenericPostRequestQuery } from 'src/store/api/app'
import { useListDomainsQuery } from 'src/store/api/domains'
+import { useListUsersQuery } from 'src/store/api/users'
import { useSelector } from 'react-redux'
const AddGroup = () => {
@@ -33,6 +35,12 @@ const AddGroup = () => {
error: domainsError,
} = useListDomainsQuery({ tenantDomain })
+ const {
+ data: users = [],
+ isFetching: usersIsFetching,
+ error: usersError,
+ } = useListUsersQuery({ tenantDomain })
+
const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
const onSubmit = (values) => {
const shippedValues = {
@@ -44,10 +52,13 @@ const AddGroup = () => {
groupType: values.groupType,
allowExternal: values.allowExternal,
membershipRules: values.membershipRules,
+ AddMember: values.AddMembers,
+ AddOwner: values.AddOwners,
}
//window.alert(JSON.stringify(shippedValues))
genericPostRequest({ path: '/api/AddGroup', values: shippedValues })
}
+
return (
@@ -90,6 +101,41 @@ const AddGroup = () => {
)}
{domainsError && Failed to load list of domains }
+
+
+
+
+ ({
+ value: user.userPrincipalName,
+ name: `${user.displayName} - ${user.userPrincipalName}`,
+ }))}
+ placeholder={!usersIsFetching ? 'Select user' : 'Loading...'}
+ name="AddOwners"
+ />
+ {usersError && Failed to load list of users }
+
+
+
+
+ ({
+ value: user.userPrincipalName,
+ name: `${user.displayName} - ${user.userPrincipalName}`,
+ }))}
+ placeholder={!usersIsFetching ? 'Select user' : 'Loading...'}
+ name="AddMembers"
+ />
+ {usersError && Failed to load list of users }
+
+
+
+
+
@@ -103,22 +149,22 @@ const AddGroup = () => {
label="Mail Enabled Security Group"
value="security"
/>
+
+
+
+
+
+
-
-
-
-
-
-
diff --git a/src/views/identity/administration/AddGroupTemplate.js b/src/views/identity/administration/AddGroupTemplate.jsx
similarity index 100%
rename from src/views/identity/administration/AddGroupTemplate.js
rename to src/views/identity/administration/AddGroupTemplate.jsx
diff --git a/src/views/identity/administration/AddUser.js b/src/views/identity/administration/AddUser.jsx
similarity index 91%
rename from src/views/identity/administration/AddUser.js
rename to src/views/identity/administration/AddUser.jsx
index ba0e0b1d96a3..a915ec793bd0 100644
--- a/src/views/identity/administration/AddUser.js
+++ b/src/views/identity/administration/AddUser.jsx
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { useEffect, useState } from 'react'
import {
CButton,
CCard,
@@ -36,6 +36,7 @@ import { required } from 'src/validators'
import useQuery from 'src/hooks/useQuery'
import Select from 'react-select'
import { useNavigate } from 'react-router-dom'
+import { OnChange } from 'react-final-form-listeners'
const AddUser = () => {
let navigate = useNavigate()
@@ -103,11 +104,7 @@ const AddUser = () => {
genericPostRequest({ path: '/api/AddUser', values: shippedValues })
}
const usagelocation = useSelector((state) => state.app.usageLocation)
- const initialState = {
- Autopassword: false,
- usageLocation: usagelocation,
- ...allQueryObj,
- }
+
const copyUserVariables = (t) => {
for (const [key, value] of Object.entries(t.value)) {
query.delete(key)
@@ -117,6 +114,20 @@ const AddUser = () => {
navigate(`?${query.toString()}`)
}
}
+
+ const [firstName, setFirstName] = useState('')
+ const [lastName, setLastName] = useState('')
+ const [displayName, setDisplayName] = useState('')
+ const initialState = {
+ Autopassword: false,
+ usageLocation: usagelocation,
+ ...allQueryObj,
+ }
+ // Effect to update display name when first or last name changes
+ useEffect(() => {
+ setDisplayName(`${firstName} ${lastName}`)
+ }, [firstName, lastName, displayName])
+
return (
{postResults.isSuccess && (
@@ -144,15 +155,25 @@ const AddUser = () => {
diff --git a/src/views/identity/administration/Deleted.js b/src/views/identity/administration/Deleted.jsx
similarity index 97%
rename from src/views/identity/administration/Deleted.js
rename to src/views/identity/administration/Deleted.jsx
index 757e3867cc6b..85eb4efc6c64 100644
--- a/src/views/identity/administration/Deleted.js
+++ b/src/views/identity/administration/Deleted.jsx
@@ -40,7 +40,7 @@ const Offcanvas = (row, rowIndex, formatExtraData) => {
color: 'info',
modal: true,
modalUrl: `/api/ExecRestoreDeleted?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`,
- modalMessage: 'Are you sure you want to create a Temporary Access Pass?',
+ modalMessage: 'Are you sure you want to restore the user?',
},
]}
placement="end"
diff --git a/src/views/identity/administration/DeployGroupTemplate.js b/src/views/identity/administration/DeployGroupTemplate.jsx
similarity index 98%
rename from src/views/identity/administration/DeployGroupTemplate.js
rename to src/views/identity/administration/DeployGroupTemplate.jsx
index 4e89504a72c6..64b730df4d43 100644
--- a/src/views/identity/administration/DeployGroupTemplate.js
+++ b/src/views/identity/administration/DeployGroupTemplate.jsx
@@ -10,7 +10,6 @@ import {
Condition,
RFFCFormCheck,
RFFCFormInput,
- RFFCFormRadio,
RFFCFormSelect,
RFFCFormTextarea,
} from 'src/components/forms'
@@ -71,6 +70,11 @@ const ApplyGroupTemplate = () => {
)
+ WhenFieldChanges.propTypes = {
+ field: PropTypes.node,
+ set: PropTypes.string,
+ }
+
const formValues = {
TemplateType: 'Admin',
}
@@ -91,6 +95,7 @@ const ApplyGroupTemplate = () => {
+ {/* eslint-disable react/prop-types */}
{(props) => (
(
{
+ const currentDate = new Date()
+ const [startDate, setStartDate] = useState(currentDate)
+
const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName)
const {
data: users = [],
isFetching: usersIsFetching,
error: usersError,
} = useListUsersQuery({ tenantDomain })
+
+ const {
+ data: recipients = [],
+ isFetching: recipientsIsFetching,
+ error: recipientsError,
+ } = useGenericGetRequestQuery({ path: `/api/ListRecipients?tenantFilter=${tenantDomain}` })
+
const currentSettings = useSelector((state) => state.app)
const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
const handleSubmit = async (values) => {
+ const unixTime = Math.floor(startDate.getTime() / 1000)
const shippedValues = {
TenantFilter: tenantDomain,
OOO: values.OOO ? values.OOO : '',
@@ -61,6 +80,10 @@ const OffboardingWizard = () => {
removeMobile: values.RemoveMobile,
keepCopy: values.keepCopy,
removePermissions: values.removePermissions,
+ Scheduled: values.Scheduled?.enabled ? { enabled: true, date: unixTime } : { enabled: false },
+ PostExecution: values.Scheduled?.enabled
+ ? { webhook: values.webhook, psa: values.psa, email: values.email }
+ : '',
}
//alert(JSON.stringify(values, null, 2))
@@ -77,7 +100,6 @@ const OffboardingWizard = () => {
title="Tenant Choice"
description="Choose the tenant in which to offboard a user"
>
- {console.log(currentSettings.offboardingDefaults)}
Step 1
Choose a tenant
@@ -179,7 +201,7 @@ const OffboardingWizard = () => {
/>
x.mail)
.map((user) => ({
value: user.mail,
@@ -194,6 +216,34 @@ const OffboardingWizard = () => {
/>
+
+
+
+
+
+
+
+
+
+ Scheduled Offboarding Date
+ setStartDate(date)}
+ />
+
+
+ Send results to
+
+
+
+
+
+
@@ -218,6 +268,7 @@ const OffboardingWizard = () => {
)}
{!postResults.isSuccess && (
+ {/* eslint-disable react/prop-types */}
{(props) => (
<>
diff --git a/src/views/identity/administration/Roles.js b/src/views/identity/administration/Roles.jsx
similarity index 100%
rename from src/views/identity/administration/Roles.js
rename to src/views/identity/administration/Roles.jsx
diff --git a/src/views/identity/administration/User365Management.js b/src/views/identity/administration/User365Management.jsx
similarity index 100%
rename from src/views/identity/administration/User365Management.js
rename to src/views/identity/administration/User365Management.jsx
diff --git a/src/views/identity/administration/UserActions.js b/src/views/identity/administration/UserActions.jsx
similarity index 92%
rename from src/views/identity/administration/UserActions.js
rename to src/views/identity/administration/UserActions.jsx
index 774d27e481c3..756e349914bf 100644
--- a/src/views/identity/administration/UserActions.js
+++ b/src/views/identity/administration/UserActions.jsx
@@ -10,6 +10,7 @@ import {
faEllipsisH,
faEnvelope,
faEdit,
+ faKey,
} from '@fortawesome/free-solid-svg-icons'
import { ActionContentCard } from 'src/components/contentcards'
import { useLazyGenericGetRequestQuery } from 'src/store/api/app'
@@ -75,7 +76,7 @@ export default function UserActions({ tenantDomain, userId, userEmail, className
),
},
{
- label: 'Block Sign In',
+ label: 'Block Sign-In',
link: '#',
icon: faBan,
onClick: () =>
@@ -84,6 +85,16 @@ export default function UserActions({ tenantDomain, userId, userEmail, className
`/api/ExecDisableUser?TenantFilter=${tenantDomain}&ID=${userId}`,
),
},
+ {
+ label: 'Generate TAP',
+ link: '#',
+ icon: faKey,
+ onClick: () =>
+ handleModal(
+ 'Are you sure you want to generate a Temporary Access Pass for this user?',
+ `/api/ExecCreateTAP?TenantFilter=${tenantDomain}&ID=${userId}`,
+ ),
+ },
{
label: 'Reset Password',
link: '#',
diff --git a/src/views/identity/administration/UserCAPs.js b/src/views/identity/administration/UserCAPs.jsx
similarity index 100%
rename from src/views/identity/administration/UserCAPs.js
rename to src/views/identity/administration/UserCAPs.jsx
diff --git a/src/views/identity/administration/UserDetails.js b/src/views/identity/administration/UserDetails.jsx
similarity index 95%
rename from src/views/identity/administration/UserDetails.js
rename to src/views/identity/administration/UserDetails.jsx
index 8fc0d182b266..52f5e0666430 100644
--- a/src/views/identity/administration/UserDetails.js
+++ b/src/views/identity/administration/UserDetails.jsx
@@ -8,6 +8,10 @@ export default function UserDetails({ tenantDomain, userId, className = null })
const { data: user = {}, isFetching, error } = useListUserQuery({ tenantDomain, userId })
const content = [
+ {
+ heading: 'Sign-In Status',
+ body: user.accountEnabled ? 'Enabled' : 'Blocked',
+ },
{
heading: 'First Name',
body: user.givenName,
diff --git a/src/views/identity/administration/UserDevices.js b/src/views/identity/administration/UserDevices.jsx
similarity index 100%
rename from src/views/identity/administration/UserDevices.js
rename to src/views/identity/administration/UserDevices.jsx
diff --git a/src/views/identity/administration/UserEmailDetails.js b/src/views/identity/administration/UserEmailDetails.jsx
similarity index 100%
rename from src/views/identity/administration/UserEmailDetails.js
rename to src/views/identity/administration/UserEmailDetails.jsx
diff --git a/src/views/identity/administration/UserEmailPermissions.js b/src/views/identity/administration/UserEmailPermissions.jsx
similarity index 100%
rename from src/views/identity/administration/UserEmailPermissions.js
rename to src/views/identity/administration/UserEmailPermissions.jsx
diff --git a/src/views/identity/administration/UserEmailSettings.js b/src/views/identity/administration/UserEmailSettings.jsx
similarity index 96%
rename from src/views/identity/administration/UserEmailSettings.js
rename to src/views/identity/administration/UserEmailSettings.jsx
index 2fbdfe09828b..731591e45833 100644
--- a/src/views/identity/administration/UserEmailSettings.js
+++ b/src/views/identity/administration/UserEmailSettings.jsx
@@ -11,6 +11,10 @@ const formatter = (cell, warning = false, reverse = false, colourless = false) =
export default function UserEmailSettings({ userId, tenantDomain, className = null }) {
const { data: details, isFetching, error } = useListMailboxDetailsQuery({ userId, tenantDomain })
const content = [
+ {
+ heading: 'Mailbox Type',
+ body: details?.RecipientTypeDetails,
+ },
{
heading: 'User Not Restricted',
body: formatter(details?.BlockedForSpam, false, true),
diff --git a/src/views/identity/administration/UserEmailUsage.js b/src/views/identity/administration/UserEmailUsage.jsx
similarity index 100%
rename from src/views/identity/administration/UserEmailUsage.js
rename to src/views/identity/administration/UserEmailUsage.jsx
diff --git a/src/views/identity/administration/UserGroups.js b/src/views/identity/administration/UserGroups.jsx
similarity index 100%
rename from src/views/identity/administration/UserGroups.js
rename to src/views/identity/administration/UserGroups.jsx
diff --git a/src/views/identity/administration/UserLastLoginDetails.js b/src/views/identity/administration/UserLastLoginDetails.jsx
similarity index 100%
rename from src/views/identity/administration/UserLastLoginDetails.js
rename to src/views/identity/administration/UserLastLoginDetails.jsx
diff --git a/src/views/identity/administration/UserMailboxRuleList.js b/src/views/identity/administration/UserMailboxRuleList.jsx
similarity index 96%
rename from src/views/identity/administration/UserMailboxRuleList.js
rename to src/views/identity/administration/UserMailboxRuleList.jsx
index acb19e3e44e1..4ab999081ee8 100644
--- a/src/views/identity/administration/UserMailboxRuleList.js
+++ b/src/views/identity/administration/UserMailboxRuleList.jsx
@@ -1,6 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
-import { cellBooleanFormatter, CellTip } from 'src/components/tables'
+import { CellBoolean, cellBooleanFormatter, CellTip } from 'src/components/tables'
import { DatatableContentCard } from 'src/components/contentcards'
import { faEnvelope } from '@fortawesome/free-solid-svg-icons'
diff --git a/src/views/identity/administration/UserOneDriveUsage.js b/src/views/identity/administration/UserOneDriveUsage.jsx
similarity index 100%
rename from src/views/identity/administration/UserOneDriveUsage.js
rename to src/views/identity/administration/UserOneDriveUsage.jsx
diff --git a/src/views/identity/administration/UserSigninLogs.js b/src/views/identity/administration/UserSigninLogs.jsx
similarity index 100%
rename from src/views/identity/administration/UserSigninLogs.js
rename to src/views/identity/administration/UserSigninLogs.jsx
diff --git a/src/views/identity/administration/Users.js b/src/views/identity/administration/Users.jsx
similarity index 84%
rename from src/views/identity/administration/Users.js
rename to src/views/identity/administration/Users.jsx
index 40bf15590a8e..f782b436c629 100644
--- a/src/views/identity/administration/Users.js
+++ b/src/views/identity/administration/Users.jsx
@@ -1,15 +1,16 @@
-import React, { useEffect, useState } from 'react'
-import { CButton } from '@coreui/react'
-import { Link } from 'react-router-dom'
+import React, { useEffect, useState, useRef } from 'react'
+import { CButton, CFormInput, CFormLabel } from '@coreui/react'
+import { Link, useSearchParams } from 'react-router-dom'
import { useSelector } from 'react-redux'
import { faEdit, faEllipsisV, faEye } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { cellBooleanFormatter, CellTip } from 'src/components/tables'
import { CippPageList } from 'src/components/layout'
import { TitleButton } from 'src/components/buttons'
-import { CippActionsOffcanvas } from 'src/components/utilities'
+import { CippActionsOffcanvas, ModalService } from 'src/components/utilities'
import { cellLicenseFormatter, CellLicense } from 'src/components/tables/CellLicense'
import M365Licenses from 'src/data/M365Licenses'
+import CippGraphUserFilter from 'src/components/utilities/CippGraphUserFilter'
const Offcanvas = (row, rowIndex, formatExtraData) => {
const tenant = useSelector((state) => state.app.currentTenant)
@@ -49,7 +50,10 @@ const Offcanvas = (row, rowIndex, formatExtraData) => {
{
{ label: 'Mail', value: `${row.mail ?? ' '}` },
{ label: 'City', value: `${row.city ?? ' '}` },
{ label: 'Department', value: `${row.department ?? ' '}` },
- { label: 'OnPrem Last Sync', value: `${row.onPremisesLastSyncDateTime ?? ' '}` },
+ {
+ label: 'OnPrem Last Sync',
+ value: `${row.onPremisesLastSyncDateTime ?? ' '}`,
+ },
{ label: 'Unique ID', value: `${row.id ?? ' '}` },
]}
actions={[
@@ -81,11 +88,6 @@ const Offcanvas = (row, rowIndex, formatExtraData) => {
link: `/identity/administration/ViewBec?userId=${row.id}&tenantDomain=${tenant.defaultDomainName}&ID=${row.userPrincipalName}`,
color: 'info',
},
- {
- label: 'Offboard User',
- link: OffboardLink,
- color: 'info',
- },
{
label: 'Create Temporary Access Password',
color: 'info',
@@ -133,6 +135,54 @@ const Offcanvas = (row, rowIndex, formatExtraData) => {
},
modalMessage: 'Select the sharepoint site to create a shortcut for',
},
+ {
+ label: 'Add to group',
+ color: 'info',
+ modal: true,
+ modalType: 'POST',
+ modalBody: {
+ Addmember: {
+ value: row.userPrincipalName,
+ },
+ TenantId: tenant.defaultDomainName,
+ },
+ modalUrl: `/api/EditGroup`,
+ modalDropdown: {
+ url: `/api/listGroups?TenantFilter=${tenant.defaultDomainName}`,
+ labelField: 'displayName',
+ valueField: 'id',
+ addedField: {
+ groupId: 'id',
+ groupType: 'calculatedGroupType',
+ groupName: 'displayName',
+ },
+ },
+ modalMessage: 'Select the group to add the user to',
+ },
+ {
+ label: 'Remove from group',
+ color: 'info',
+ modal: true,
+ modalType: 'POST',
+ modalBody: {
+ Removemember: {
+ value: row.userPrincipalName,
+ },
+ TenantId: tenant.defaultDomainName,
+ },
+ modalUrl: `/api/EditGroup`,
+ modalDropdown: {
+ url: `/api/listGroups?TenantFilter=${tenant.defaultDomainName}`,
+ labelField: 'displayName',
+ valueField: 'id',
+ addedField: {
+ groupId: 'id',
+ groupType: 'calculatedGroupType',
+ groupName: 'displayName',
+ },
+ },
+ modalMessage: 'Select the group to add the user to',
+ },
{
label: 'Enable Online Archive',
color: 'info',
@@ -210,13 +260,6 @@ const Offcanvas = (row, rowIndex, formatExtraData) => {
modalUrl: `/api/ExecResetPass?MustChange=false&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&displayName=${row.displayName}`,
modalMessage: 'Are you sure you want to reset the password for this user?',
},
- {
- label: 'Clear ImmutableId',
- color: 'warning',
- modal: true,
- modalUrl: `/api/ExecClrImmId?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`,
- modalMessage: 'Are you sure you want to clear the ImmutableId for this user?',
- },
{
label: 'Revoke all user sessions',
color: 'danger',
@@ -241,8 +284,43 @@ const Offcanvas = (row, rowIndex, formatExtraData) => {
)
}
+const UserSearch = () => {
+ const [searchParams, setSearchParams] = useSearchParams()
+ const inputRef = useRef()
+
+ function handleModal() {
+ ModalService.confirm({
+ body: (
+ <>
+
+ Search for a user by name or email address. (Email domain is also supported).
+
+
+ >
+ ),
+ title: 'User Search',
+ onConfirm: () => {
+ if (inputRef.current.value !== '') {
+ setSearchParams({
+ tableFilter: 'Graph: ' + CippGraphUserFilter(inputRef?.current?.value),
+ updateTableFilter: true,
+ })
+ }
+ },
+ confirmLabel: 'Search',
+ })
+ }
+
+ return (
+ handleModal()}>
+
+
+ )
+}
+
const Users = (row) => {
const [tenantColumnSet, setTenantColumn] = useState(true)
+
const columns = [
{
name: 'Tenant',
@@ -328,7 +406,7 @@ const Users = (row) => {
if (tenant.defaultDomainName !== 'AllTenants') {
setTenantColumn(true)
}
- }, [tenantColumnSet])
+ }, [tenant.defaultDomainName, tenantColumnSet])
const titleButtons = (
@@ -342,6 +420,7 @@ const Users = (row) => {
)
+
return (
{
{ filterName: 'Enabled users', filter: '"accountEnabled":true' },
{ filterName: 'Disabled users', filter: '"accountEnabled":false' },
{ filterName: 'AAD users', filter: '"onPremisesSyncEnabled":false' },
- { filterName: 'Synced users', filter: '"onPremisesSyncEnabled":true' },
+ {
+ filterName: 'Synced users',
+ filter: '"onPremisesSyncEnabled":true',
+ },
{ filterName: 'Guest users', filter: '"usertype":"guest"' },
- { filterName: 'Users with a license', filter: '"assignedLicenses":[{' },
- { filterName: 'Users without a license', filter: '"assignedLicenses":[]' },
+ {
+ filterName: 'Users with a license',
+ filter: '"assignedLicenses":[{',
+ },
+ {
+ filterName: 'Users without a license',
+ filter: '"assignedLicenses":[]',
+ },
{
filterName: 'Users with a license (Graph)',
filter: 'Graph: assignedLicenses/$count ne 0',
@@ -377,7 +465,9 @@ const Users = (row) => {
$orderby: 'displayName',
},
tableProps: {
+ keyField: 'id',
selectableRows: true,
+ actions: [ ],
actionsList: [
{
label: 'Convert to Shared Mailbox',
@@ -395,7 +485,7 @@ const Users = (row) => {
label: 'Enable Online Archive',
color: 'info',
modal: true,
- modalUrl: `/api/ExecEnableArchive?TenantFilter=!Tenant&ID=!id`,
+ modalUrl: `/api/ExecEnableArchive?TenantFilter=!Tenant&ID=!userPrincipalName`,
modalMessage: 'Are you sure you want to enable the online archive for these users?',
},
{
diff --git a/src/views/identity/administration/ViewBEC.js b/src/views/identity/administration/ViewBEC.jsx
similarity index 83%
rename from src/views/identity/administration/ViewBEC.js
rename to src/views/identity/administration/ViewBEC.jsx
index bfe147e850cb..4f1173042008 100644
--- a/src/views/identity/administration/ViewBEC.js
+++ b/src/views/identity/administration/ViewBEC.jsx
@@ -1,6 +1,5 @@
import React, { useEffect } from 'react'
-import { CButton, CCallout, CLink, CCardTitle } from '@coreui/react'
-import { CCardBody, CSpinner } from '@coreui/react'
+import { CButton, CCallout, CLink, CCardTitle, CSpinner } from '@coreui/react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {
faCheckCircle,
@@ -33,8 +32,8 @@ const ViewBec = () => {
const [execBecView, results] = useLazyExecBecCheckQuery()
const { data: alerts = {}, isFetching, error, isSuccess } = results
useEffect(() => {
- execBecView({ tenantFilter: tenantDomain, userId: userId })
- }, [execBecView, tenantDomain, userId])
+ execBecView({ tenantFilter: tenantDomain, userId: userId, userName: userName })
+ }, [execBecView, tenantDomain, userId, userName])
const deviceColumns = [
{
@@ -104,72 +103,67 @@ const ViewBec = () => {
selector: (row) => row.Status,
sortable: true,
},
+ {
+ name: 'IP',
+ selector: (row) => row.IPAddress,
+ sortable: true,
+ },
]
const mailboxlogonColumns = [
{
name: 'IP',
- selector: (row) => row['ClientIP'],
+ selector: (row) => row['IPAddress'],
sortable: true,
},
{
name: 'User',
- selector: (row) => row['CreatedDateTime'],
+ selector: (row) => row['userPrincipalName'],
sortable: true,
},
{
- name: 'User Agent',
- selector: (row) => row['ClientInfoString'],
+ name: 'Application',
+ selector: (row) => row['AppDisplayName'],
sortable: true,
},
{
name: 'Result',
- selector: (row) => row['ResultStatus'],
- sortable: true,
- },
- {
- name: 'Data',
- selector: (row) => row['CreationTime'],
+ selector: (row) => row['Status'],
sortable: true,
},
]
const newUserColumns = [
{
- name: 'Username',
- selector: (row) => row['ObjectId'],
+ name: 'DisplayName',
+ selector: (row) => row['displayName'],
sortable: true,
},
{
- name: 'Date',
- selector: (row) => row['CreationTime'],
+ name: 'Username',
+ selector: (row) => row['userPrincipalName'],
sortable: true,
},
{
- name: 'By',
- selector: (row) => row['UserId'],
+ name: 'Date',
+ selector: (row) => row['CreatedDateTime'],
sortable: true,
},
]
const passwordColumns = [
{
- name: 'Username',
- selector: (row) => row['ObjectId'],
- sortable: true,
- },
- {
- name: 'Date',
- selector: (row) => row['CreationTime'],
+ name: 'displayName',
+ selector: (row) => row['displayName'],
sortable: true,
},
{
- name: 'Operation',
- selector: (row) => row['Operation'],
+ name: 'Username',
+ selector: (row) => row['userPrincipalName'],
sortable: true,
},
{
- name: 'By',
- selector: (row) => row['UserId'],
+ name: 'Date',
+ selector: (row) => row['lastPasswordChangeDateTime'],
sortable: true,
},
]
@@ -224,7 +218,11 @@ const ViewBec = () => {
onConfirm: () => {
execBecRemediate({
path: '/api/execBecRemediate',
- values: { userId: userId, tenantFilter: tenantDomain },
+ values: {
+ userId: userId,
+ tenantFilter: tenantDomain,
+ userName: userName,
+ },
})
},
})
@@ -238,7 +236,11 @@ const ViewBec = () => {
- execBecView({ tenantFilter: tenantDomain, userId: userId, overwrite: true })
+ execBecView({
+ tenantFilter: tenantDomain,
+ userId: userId,
+ overwrite: true,
+ })
}
disabled={isFetching}
>
@@ -283,7 +285,11 @@ const ViewBec = () => {
)}
{execRemediateResults.isSuccess && (
- {execRemediateResults.data?.Results}
+
+ {execRemediateResults.data?.Results.map((item, idx) => {
+ return {item}
+ })}
+
)}
@@ -305,7 +311,7 @@ const ViewBec = () => {
-
+
{isFetching && }
{isSuccess && (
@@ -316,7 +322,6 @@ const ViewBec = () => {
striped
responsive={true}
tableProps={{ subHeaderComponent: false }}
- wrapperClasses="table-responsive"
reportName="none"
/>
)}
@@ -325,25 +330,21 @@ const ViewBec = () => {
{isFetching && }
-
-
- {isSuccess && (
-
- )}
-
+ {isSuccess && (
+
+ )}
-
+
{isFetching && }
{isSuccess && (
@@ -361,7 +362,7 @@ const ViewBec = () => {
-
+
{isFetching && }
{isSuccess && (
@@ -379,7 +380,7 @@ const ViewBec = () => {
-
+
{isFetching && }
{isSuccess && (
@@ -397,7 +398,7 @@ const ViewBec = () => {
-
+
{isFetching && }
{isSuccess && (
@@ -415,7 +416,7 @@ const ViewBec = () => {
-
+
{isFetching && }
{isSuccess && (
diff --git a/src/views/identity/administration/ViewGroup.js b/src/views/identity/administration/ViewGroup.jsx
similarity index 100%
rename from src/views/identity/administration/ViewGroup.js
rename to src/views/identity/administration/ViewGroup.jsx
diff --git a/src/views/identity/administration/ViewUser.js b/src/views/identity/administration/ViewUser.jsx
similarity index 97%
rename from src/views/identity/administration/ViewUser.js
rename to src/views/identity/administration/ViewUser.jsx
index 9e7f39feb4c8..ae0e44510693 100644
--- a/src/views/identity/administration/ViewUser.js
+++ b/src/views/identity/administration/ViewUser.jsx
@@ -3,8 +3,7 @@ import { CSpinner } from '@coreui/react'
import PropTypes from 'prop-types'
import useQuery from 'src/hooks/useQuery'
import { useDispatch } from 'react-redux'
-import { CippPage } from 'src/components/layout'
-import { CippMasonry, CippMasonryItem } from 'src/components/layout'
+import { CippPage, CippMasonry, CippMasonryItem } from 'src/components/layout'
import { ModalService } from 'src/components/utilities'
import UserDevices from 'src/views/identity/administration/UserDevices'
import UserDetails from 'src/views/identity/administration/UserDetails'
diff --git a/src/views/identity/reports/AzureADConnectReport.js b/src/views/identity/reports/AzureADConnectReport.jsx
similarity index 100%
rename from src/views/identity/reports/AzureADConnectReport.js
rename to src/views/identity/reports/AzureADConnectReport.jsx
diff --git a/src/views/identity/reports/InactiveUsers.js b/src/views/identity/reports/InactiveUsers.jsx
similarity index 100%
rename from src/views/identity/reports/InactiveUsers.js
rename to src/views/identity/reports/InactiveUsers.jsx
diff --git a/src/views/identity/reports/MFAReport.js b/src/views/identity/reports/MFAReport.jsx
similarity index 100%
rename from src/views/identity/reports/MFAReport.js
rename to src/views/identity/reports/MFAReport.jsx
diff --git a/src/views/identity/reports/SignIns.js b/src/views/identity/reports/SignIns.jsx
similarity index 98%
rename from src/views/identity/reports/SignIns.js
rename to src/views/identity/reports/SignIns.jsx
index aea60797072b..bcd14acc0c07 100644
--- a/src/views/identity/reports/SignIns.js
+++ b/src/views/identity/reports/SignIns.jsx
@@ -12,8 +12,7 @@ import {
} from '@coreui/react'
import { faChevronDown, faChevronRight, faSearch } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
-import React from 'react'
-import { useState } from 'react'
+import React, { useState } from 'react'
import { Form } from 'react-final-form'
import { useSelector } from 'react-redux'
import { useNavigate } from 'react-router-dom'
diff --git a/src/views/pages/LogoutRedirect/PageLogOut.js b/src/views/pages/LogoutRedirect/PageLogOut.jsx
similarity index 96%
rename from src/views/pages/LogoutRedirect/PageLogOut.js
rename to src/views/pages/LogoutRedirect/PageLogOut.jsx
index 78a4d86f5e1e..fdf040816f5a 100644
--- a/src/views/pages/LogoutRedirect/PageLogOut.js
+++ b/src/views/pages/LogoutRedirect/PageLogOut.jsx
@@ -1,6 +1,6 @@
import React from 'react'
import { CButton, CCol, CContainer, CRow } from '@coreui/react'
-import { Helmet } from 'react-helmet'
+import { Helmet } from 'react-helmet-async'
import { useSearchParams } from 'react-router-dom'
const Page401 = () => {
diff --git a/src/views/pages/license/License.js b/src/views/pages/license/License.jsx
similarity index 99%
rename from src/views/pages/license/License.js
rename to src/views/pages/license/License.jsx
index 8ec6a56b2d62..b137abf265fe 100644
--- a/src/views/pages/license/License.js
+++ b/src/views/pages/license/License.jsx
@@ -1,6 +1,6 @@
import React from 'react'
import { CCol, CContainer, CRow } from '@coreui/react'
-import { Helmet } from 'react-helmet'
+import { Helmet } from 'react-helmet-async'
const Login = () => {
return (
diff --git a/src/views/pages/login/Login.js b/src/views/pages/login/Login.jsx
similarity index 100%
rename from src/views/pages/login/Login.js
rename to src/views/pages/login/Login.jsx
diff --git a/src/views/pages/login/Logout.js b/src/views/pages/login/Logout.jsx
similarity index 100%
rename from src/views/pages/login/Logout.js
rename to src/views/pages/login/Logout.jsx
diff --git a/src/views/pages/page401/Page401.js b/src/views/pages/page401/Page401.jsx
similarity index 96%
rename from src/views/pages/page401/Page401.js
rename to src/views/pages/page401/Page401.jsx
index 3e6e44715453..b02c2d441055 100644
--- a/src/views/pages/page401/Page401.js
+++ b/src/views/pages/page401/Page401.jsx
@@ -1,6 +1,6 @@
import React from 'react'
import { CButton, CCol, CContainer, CRow } from '@coreui/react'
-import { Helmet } from 'react-helmet'
+import { Helmet } from 'react-helmet-async'
import { useSearchParams } from 'react-router-dom'
const Page401 = () => {
diff --git a/src/views/pages/page403/Page403.js b/src/views/pages/page403/Page403.jsx
similarity index 95%
rename from src/views/pages/page403/Page403.js
rename to src/views/pages/page403/Page403.jsx
index c747f80fcf6c..7804a4ef3ece 100644
--- a/src/views/pages/page403/Page403.js
+++ b/src/views/pages/page403/Page403.jsx
@@ -1,6 +1,6 @@
import React from 'react'
import { CButton, CCol, CContainer, CRow } from '@coreui/react'
-import { Helmet } from 'react-helmet'
+import { Helmet } from 'react-helmet-async'
const Page403 = () => {
return (
diff --git a/src/views/pages/page404/Page404.js b/src/views/pages/page404/Page404.jsx
similarity index 95%
rename from src/views/pages/page404/Page404.js
rename to src/views/pages/page404/Page404.jsx
index 673ae4ad5a82..49427afd83fc 100644
--- a/src/views/pages/page404/Page404.js
+++ b/src/views/pages/page404/Page404.jsx
@@ -1,7 +1,7 @@
import React from 'react'
import { CButton, CCol, CContainer, CRow } from '@coreui/react'
import { FastSwitcher } from 'src/components/utilities'
-import { Helmet } from 'react-helmet'
+import { Helmet } from 'react-helmet-async'
import { Link } from 'react-router-dom'
const Page404 = () => {
diff --git a/src/views/pages/page500/Page500.js b/src/views/pages/page500/Page500.jsx
similarity index 93%
rename from src/views/pages/page500/Page500.js
rename to src/views/pages/page500/Page500.jsx
index b046cac59d99..c58e18ae374e 100644
--- a/src/views/pages/page500/Page500.js
+++ b/src/views/pages/page500/Page500.jsx
@@ -13,7 +13,7 @@ import {
CTableHeaderCell,
CTableRow,
} from '@coreui/react'
-import { FastSwitcher } from 'src/components/utilities'
+import PropTypes from 'prop-types'
const Page500 = ({ errorcode, issue }) => {
return (
@@ -55,4 +55,9 @@ const Page500 = ({ errorcode, issue }) => {
)
}
+Page500.propTypes = {
+ errorcode: PropTypes.string,
+ issue: PropTypes.string,
+}
+
export default Page500
diff --git a/src/views/security/defender/DeployDefender.js b/src/views/security/defender/DeployDefender.jsx
similarity index 100%
rename from src/views/security/defender/DeployDefender.js
rename to src/views/security/defender/DeployDefender.jsx
diff --git a/src/views/security/defender/ListDefender.js b/src/views/security/defender/ListDefender.jsx
similarity index 100%
rename from src/views/security/defender/ListDefender.js
rename to src/views/security/defender/ListDefender.jsx
diff --git a/src/views/security/defender/ListVuln.js b/src/views/security/defender/ListVuln.jsx
similarity index 100%
rename from src/views/security/defender/ListVuln.js
rename to src/views/security/defender/ListVuln.jsx
diff --git a/src/views/security/incidents/ListAlerts.js b/src/views/security/incidents/ListAlerts.jsx
similarity index 97%
rename from src/views/security/incidents/ListAlerts.js
rename to src/views/security/incidents/ListAlerts.jsx
index cbf11c37c2cc..a197b5e654b6 100644
--- a/src/views/security/incidents/ListAlerts.js
+++ b/src/views/security/incidents/ListAlerts.jsx
@@ -1,7 +1,15 @@
import React, { useEffect, useState } from 'react'
-import { CButton, CCallout, CCardGroup, CCardText } from '@coreui/react'
+import {
+ CButton,
+ CCallout,
+ CCardGroup,
+ CCardText,
+ CCard,
+ CCardBody,
+ CCardHeader,
+ CCardTitle,
+} from '@coreui/react'
import { CippTable, cellDateFormatter, CellTip } from 'src/components/tables'
-import { CCard, CCardBody, CCardHeader, CCardTitle } from '@coreui/react'
import { useLazyExecAlertsListQuery } from 'src/store/api/security'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { CippPage } from 'src/components/layout'
diff --git a/src/views/security/incidents/ListIncidents.js b/src/views/security/incidents/ListIncidents.jsx
similarity index 98%
rename from src/views/security/incidents/ListIncidents.js
rename to src/views/security/incidents/ListIncidents.jsx
index fa8beb29730a..703409631da5 100644
--- a/src/views/security/incidents/ListIncidents.js
+++ b/src/views/security/incidents/ListIncidents.jsx
@@ -1,7 +1,15 @@
import React, { useEffect, useState } from 'react'
-import { CButton, CCallout, CCardText, CListGroupItem } from '@coreui/react'
+import {
+ CButton,
+ CCallout,
+ CCardText,
+ CListGroupItem,
+ CCard,
+ CCardBody,
+ CCardHeader,
+ CCardTitle,
+} from '@coreui/react'
import { CippTable, cellDateFormatter, CellTip } from 'src/components/tables'
-import { CCard, CCardBody, CCardHeader, CCardTitle } from '@coreui/react'
import { useLazyExecIncidentsListQuery } from 'src/store/api/security'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { CippPage } from 'src/components/layout'
diff --git a/src/views/security/reports/ListDeviceComplianceReport.js b/src/views/security/reports/ListDeviceComplianceReport.jsx
similarity index 100%
rename from src/views/security/reports/ListDeviceComplianceReport.js
rename to src/views/security/reports/ListDeviceComplianceReport.jsx
diff --git a/src/views/teams-share/onedrive/OneDriveList.js b/src/views/teams-share/onedrive/OneDriveList.jsx
similarity index 100%
rename from src/views/teams-share/onedrive/OneDriveList.js
rename to src/views/teams-share/onedrive/OneDriveList.jsx
diff --git a/src/views/teams-share/sharepoint/SharepointList.js b/src/views/teams-share/sharepoint/SharepointList.jsx
similarity index 100%
rename from src/views/teams-share/sharepoint/SharepointList.js
rename to src/views/teams-share/sharepoint/SharepointList.jsx
diff --git a/src/views/teams-share/teams/BusinessVoice.js b/src/views/teams-share/teams/BusinessVoice.jsx
similarity index 100%
rename from src/views/teams-share/teams/BusinessVoice.js
rename to src/views/teams-share/teams/BusinessVoice.jsx
diff --git a/src/views/teams-share/teams/TeamApplications.js b/src/views/teams-share/teams/TeamApplications.jsx
similarity index 100%
rename from src/views/teams-share/teams/TeamApplications.js
rename to src/views/teams-share/teams/TeamApplications.jsx
diff --git a/src/views/teams-share/teams/TeamChannels.js b/src/views/teams-share/teams/TeamChannels.jsx
similarity index 100%
rename from src/views/teams-share/teams/TeamChannels.js
rename to src/views/teams-share/teams/TeamChannels.jsx
diff --git a/src/views/teams-share/teams/TeamDetails.js b/src/views/teams-share/teams/TeamDetails.jsx
similarity index 100%
rename from src/views/teams-share/teams/TeamDetails.js
rename to src/views/teams-share/teams/TeamDetails.jsx
diff --git a/src/views/teams-share/teams/TeamGuestPolicies.js b/src/views/teams-share/teams/TeamGuestPolicies.jsx
similarity index 100%
rename from src/views/teams-share/teams/TeamGuestPolicies.js
rename to src/views/teams-share/teams/TeamGuestPolicies.jsx
diff --git a/src/views/teams-share/teams/TeamMemberPolicies.js b/src/views/teams-share/teams/TeamMemberPolicies.jsx
similarity index 100%
rename from src/views/teams-share/teams/TeamMemberPolicies.js
rename to src/views/teams-share/teams/TeamMemberPolicies.jsx
diff --git a/src/views/teams-share/teams/TeamMembers.js b/src/views/teams-share/teams/TeamMembers.jsx
similarity index 100%
rename from src/views/teams-share/teams/TeamMembers.js
rename to src/views/teams-share/teams/TeamMembers.jsx
diff --git a/src/views/teams-share/teams/TeamMessagingSettings.js b/src/views/teams-share/teams/TeamMessagingSettings.jsx
similarity index 100%
rename from src/views/teams-share/teams/TeamMessagingSettings.js
rename to src/views/teams-share/teams/TeamMessagingSettings.jsx
diff --git a/src/views/teams-share/teams/TeamOwners.js b/src/views/teams-share/teams/TeamOwners.jsx
similarity index 100%
rename from src/views/teams-share/teams/TeamOwners.js
rename to src/views/teams-share/teams/TeamOwners.jsx
diff --git a/src/views/teams-share/teams/TeamsActivity.js b/src/views/teams-share/teams/TeamsActivity.jsx
similarity index 100%
rename from src/views/teams-share/teams/TeamsActivity.js
rename to src/views/teams-share/teams/TeamsActivity.jsx
diff --git a/src/views/teams-share/teams/TeamsAddTeam.js b/src/views/teams-share/teams/TeamsAddTeam.jsx
similarity index 100%
rename from src/views/teams-share/teams/TeamsAddTeam.js
rename to src/views/teams-share/teams/TeamsAddTeam.jsx
diff --git a/src/views/teams-share/teams/TeamsListTeam.js b/src/views/teams-share/teams/TeamsListTeam.jsx
similarity index 100%
rename from src/views/teams-share/teams/TeamsListTeam.js
rename to src/views/teams-share/teams/TeamsListTeam.jsx
diff --git a/src/views/teams-share/teams/ViewTeamSettings.js b/src/views/teams-share/teams/ViewTeamSettings.jsx
similarity index 100%
rename from src/views/teams-share/teams/ViewTeamSettings.js
rename to src/views/teams-share/teams/ViewTeamSettings.jsx
diff --git a/src/views/tenant/administration/AlertRules.jsx b/src/views/tenant/administration/AlertRules.jsx
new file mode 100644
index 000000000000..d7c6c554220d
--- /dev/null
+++ b/src/views/tenant/administration/AlertRules.jsx
@@ -0,0 +1,541 @@
+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 { useSelector } from 'react-redux'
+import { Field, Form, FormSpy } from 'react-final-form'
+import {
+ Condition,
+ RFFCFormInput,
+ RFFCFormInputArray,
+ RFFCFormSelect,
+ RFFCFormSwitch,
+ RFFSelectSearch,
+} from 'src/components/forms'
+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 { cellBadgeFormatter, cellDateFormatter } from 'src/components/tables'
+import { CellTip } from 'src/components/tables/CellGenericFormat'
+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'
+import arrayMutators from 'final-form-arrays'
+import countryList from 'src/data/countryList.json'
+
+const AlertRules = () => {
+ const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery()
+
+ const Offcanvas = (row, rowIndex, formatExtraData) => {
+ const [ocVisible, setOCVisible] = useState(false)
+ const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName)
+
+ 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/RemoveWebhookAlert?Tenantfilter=${row.Tenant}&ID=${row.RowKey}`,
+ 'Do you want to delete this job?',
+ )
+ }
+ size="sm"
+ variant="ghost"
+ color="danger"
+ >
+
+
+
+ setOCVisible(false)}
+ />
+ >
+ )
+ }
+ 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) => {
+ values['tenantfilter'] = tenantDomain
+ genericPostRequest({ path: '/api/AddAlert', values: values }).then((res) => {
+ setRefreshState(res.requestId)
+ })
+ }
+ const columns = [
+ {
+ name: 'Tenant',
+ selector: (row) => row['Tenant'],
+ sortable: true,
+ cell: (row) => CellTip(row['Tenant']),
+ exportSelector: 'Tenant',
+ },
+ {
+ name: 'If',
+ selector: (row) => row['if'],
+ sortable: true,
+ cell: cellBadgeFormatter(),
+ exportSelector: 'if',
+ },
+ {
+ name: 'Execute',
+ selector: (row) => row['execution'],
+ sortable: true,
+ cell: (row) => CellTip(row['execution']),
+ exportSelector: 'execution',
+ },
+ {
+ name: 'Actions',
+ cell: Offcanvas,
+ maxWidth: '80px',
+ },
+ ]
+
+ const ifvalues = [
+ { value: 'New-InboxRule', label: 'A new Inbox rule is created' },
+ { value: 'Set-InboxRule', label: 'A existing Inbox rule is edited' },
+ {
+ value: 'Add member to role.',
+ label: 'A user has been added to an admin role',
+ },
+ {
+ value: 'Disable account.',
+ label: 'A user account has been disabled',
+ },
+ {
+ value: 'Enable account.',
+ label: 'A user account has been enabled',
+ },
+ {
+ value: 'Update StsRefreshTokenValidFrom Timestamp.',
+ label: 'A user sessions have been revoked',
+ },
+ {
+ value: 'Disable Strong Authentication.',
+ label: 'A users MFA has been disabled',
+ },
+ {
+ value: 'Remove Member from a role.',
+ label: 'A user has been removed from a role',
+ },
+ {
+ value: 'Reset user password.',
+ label: 'A user password has been reset',
+ },
+ {
+ value: 'UserLoggedInFromUnknownLocation',
+ label: 'A user has logged in from a location not in the allowed locations list',
+ },
+ {
+ value: 'Add service principal',
+ label: 'A service principal has been created',
+ },
+ {
+ value: 'Remove service principal.',
+ label: 'A service principal has been removed',
+ },
+ // {
+ // value: 'ImpossibleTravel',
+ // label: 'A user has logged in from an impossible location (Based on IP)',
+ // },
+ {
+ value: 'badRepIP',
+ label: 'A user has logged in a using a known VPN, Proxy, Or anonymizer',
+ },
+ {
+ value: 'HostedIP',
+ label: 'A user has logged in a using a known hosting provider IP',
+ },
+ { value: 'customField', label: 'Custom Log Query' },
+ { value: 'anyAlert', label: 'Any alert has been received' },
+ ]
+
+ const customIfValues = [{ value: 'customField', label: 'Custom Log Query' }]
+ const dovalues = [
+ { value: 'cippcommand', label: 'Execute a CIPP Command' },
+ { value: 'becremediate', label: 'Execute a BEC Remediate' },
+ { value: 'disableuser', label: 'Disable the user in the log entry' },
+ // { value: 'generatelog', label: 'Generate a log entry' },
+ { value: 'generatemail', label: 'Generate an email' },
+ { value: 'generatePSA', label: 'Generate a PSA ticket' },
+ { value: 'generateWebhook', label: 'Generate a webhook' },
+ {
+ value: 'store',
+ label: 'Store the log into an external Azure Storage Account',
+ },
+ ]
+
+ const [ifCount, setIfCount] = useState(1)
+ const [doCount, setDoCount] = useState(1)
+
+ const handleButtonIf = (operator) => {
+ if (operator === '+') {
+ if (ifCount < 3) {
+ setIfCount(ifCount + 1)
+ }
+ } else {
+ if (ifCount > 1) {
+ setIfCount(ifCount - 1)
+ }
+ }
+ }
+
+ const handleButtonDo = (operator) => {
+ if (operator === '+') {
+ if (doCount < 10) {
+ setDoCount(doCount + 1)
+ }
+ } else {
+ if (doCount > 1) {
+ setDoCount(doCount - 1)
+ }
+ }
+ }
+
+ const renderIfs = () => {
+ const ifs = []
+ for (let i = 0; i < ifCount; i++) {
+ ifs.push(
+ <>
+ {i === 0 ? 'If' : 'And'}
+
+
+
+
+
+ {ifCount > 1 && (
+ handleButtonIf('-')}
+ disabled={doCount >= 10}
+ >
+
+
+ )}
+
+
+ handleButtonIf('+')}
+ disabled={ifCount >= 3}
+ >
+
+
+
+
+
+
+
+
+
+
+ ({
+ value: Code,
+ name: Name,
+ }))}
+ />
+
+
+
+ >,
+ )
+ }
+ return ifs
+ }
+
+ const renderDos = () => {
+ const dos = []
+
+ for (let i = 0; i < doCount; i++) {
+ dos.push(
+ <>
+ {i === 0 ? 'Execute this' : 'And'}
+
+
+
+
+ {doCount > 1 && (
+
+ handleButtonDo('-')}
+ disabled={doCount >= 10}
+ >
+
+
+
+ )}
+
+ handleButtonDo('+')}
+ disabled={doCount >= 10}
+ >
+
+
+
+
+
+
+ ({
+ value: cmd.Function,
+ name: cmd.Function,
+ }))}
+ name={`do.${i}.command`}
+ key={`do.${i}.command`}
+ placeholder={
+ isLoadingcmd ? (
+
+ ) : (
+ 'Select a command or report to execute.'
+ )
+ }
+ label="Command to execute"
+ />
+
+
+
+
+
+
+
+
+
+
+
+ >,
+ )
+ }
+ return dos
+ }
+
+ return (
+
+ <>
+
+
+
+
+
+
+
+
+
+ >
+
+ )
+}
+
+export default AlertRules
diff --git a/src/views/tenant/administration/AlertWizard.js b/src/views/tenant/administration/AlertWizard.jsx
similarity index 80%
rename from src/views/tenant/administration/AlertWizard.js
rename to src/views/tenant/administration/AlertWizard.jsx
index a8986663644a..c2e920354e15 100644
--- a/src/views/tenant/administration/AlertWizard.js
+++ b/src/views/tenant/administration/AlertWizard.jsx
@@ -9,6 +9,7 @@ import PropTypes from 'prop-types'
import { Condition, RFFCFormSelect, RFFCFormSwitch, RFFSelectSearch } from 'src/components/forms'
import { useLazyGenericPostRequestQuery } from 'src/store/api/app'
import countryList from 'src/data/countryList.json'
+
const Error = ({ name }) => (
{
Step 2
- Select alerts to receive
+ Select Legacy Alerts to receive
- Alerts setup on this page will be sent to webhook configured in CIPPs settings, and be
- delivered as messages
+ Alerts setup on this page are considered legacy. These alerts run every 15 minutes and
+ do not use our advanced alerting engine.
{
/>
+
{
name="NoCAConfig"
label="Alert on tenants without a Conditional Access policy, while having Conditional Access licensing available."
/>
-
+
+
+
+
+
+
+
{
name="DefenderMalware"
label="Alert on Defender Malware found (Tenant must be on-boarded in Lighthouse)"
/>
-
-
-
-
-
-
{
-
-
@@ -167,78 +167,15 @@ const AlertWizard = () => {
- These alerts are received directly from the audit log, and will be processed as soon as
- Microsoft sends them to CIPP. These alerts generate a ticket, email or webhook message
- per alert, with more information about the alert.
-
-
-
- "Alerts setup on this page will be sent to the webhook configured in CIPPs settings, and
- be delivered as raw json information. Warning: Teams, Slack, and Discord do not support
- receiving raw json messages"
+ This setting will subscribe CIPP to receive the audit logs from this tenant directly.
+ You can then use the Alert Rules page to create alerts or take actions based on these
+ logs.
-
-
-
-
-
-
- ({
- value: Code,
- name: Name,
- }))}
+
@@ -253,6 +190,7 @@ const AlertWizard = () => {
{!postResults.isSuccess && (
+ {/* eslint-disable react/prop-types */}
{(props) => {
return (
<>
diff --git a/src/views/tenant/administration/Domains.js b/src/views/tenant/administration/Domains.jsx
similarity index 100%
rename from src/views/tenant/administration/Domains.js
rename to src/views/tenant/administration/Domains.jsx
diff --git a/src/views/tenant/administration/EditTenant.js b/src/views/tenant/administration/EditTenant.jsx
similarity index 100%
rename from src/views/tenant/administration/EditTenant.js
rename to src/views/tenant/administration/EditTenant.jsx
diff --git a/src/views/tenant/administration/GDAPInviteWizard.js b/src/views/tenant/administration/GDAPInviteWizard.jsx
similarity index 58%
rename from src/views/tenant/administration/GDAPInviteWizard.js
rename to src/views/tenant/administration/GDAPInviteWizard.jsx
index 1efdd36f7906..2377ae2e0239 100644
--- a/src/views/tenant/administration/GDAPInviteWizard.js
+++ b/src/views/tenant/administration/GDAPInviteWizard.jsx
@@ -1,13 +1,24 @@
-import React from 'react'
-import { CCol, CRow, CForm, CCallout, CSpinner, CButton } from '@coreui/react'
+import React, { useState } from 'react'
+import {
+ CCol,
+ CRow,
+ CForm,
+ CCallout,
+ CSpinner,
+ CFormInput,
+ CFormLabel,
+ CFormRange,
+ CProgress,
+} from '@coreui/react'
import { Field, FormSpy } from 'react-final-form'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'
import { CippWizard } from 'src/components/layout'
-import { WizardTableField } from 'src/components/tables'
+import { CippTable, WizardTableField } from 'src/components/tables'
import { TitleButton } from 'src/components/buttons'
import PropTypes from 'prop-types'
import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app'
+import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat'
const Error = ({ name }) => (
(value && value.length !== 0 ? undefined : 'Required')
const GDAPInviteWizard = () => {
+ const [inviteCount, setInviteCount] = useState(1)
+ const [loopRunning, setLoopRunning] = React.useState(false)
+ const [massResults, setMassResults] = React.useState([])
const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
const [genericGetRequest, getResults] = useLazyGenericGetRequestQuery()
const handleSubmit = async (values) => {
- genericPostRequest({ path: '/api/ExecGDAPInvite', values: values })
+ const resultsarr = []
+ setLoopRunning(true)
+ for (var x = 0; x < inviteCount; x++) {
+ const results = await genericPostRequest({ path: '/api/ExecGDAPInvite', values: values })
+ resultsarr.push(results.data)
+ setMassResults(resultsarr)
+ }
+ setLoopRunning(false)
}
const formValues = {}
+ const inviteColumns = [
+ {
+ name: 'Id',
+ selector: (row) => row?.Invite?.RowKey,
+ exportSelector: 'Invite/RowKey',
+ sortable: true,
+ omit: true,
+ cell: cellGenericFormatter(),
+ },
+ {
+ name: 'Invite Link',
+ sortable: true,
+ selector: (row) => row?.Invite?.InviteUrl,
+ exportSelector: 'Invite/InviteUrl',
+ cell: cellGenericFormatter(),
+ },
+ {
+ name: 'Onboarding Link',
+ sortable: true,
+ selector: (row) => row?.Invite?.OnboardingUrl,
+ exportSelector: 'Invite/OnboardingUrl',
+ cell: cellGenericFormatter(),
+ },
+ {
+ name: 'Message',
+ sortable: true,
+ selector: (row) => row?.Message,
+ exportSelector: 'Message',
+ cell: cellGenericFormatter(),
+ },
+ ]
+
return (
{
+
{(props) => (
{
-
+
Step 2
+ Invite Options
+
+
+ Number of Invites
+
+
+ setInviteCount(e.target.value)}
+ />
+
+
+ setInviteCount(e.target.value)}
+ />
+
+
+
+
+
+ Step 3
Confirm and apply
- {!postResults.isSuccess && (
+ {massResults.length < 1 && (
+ {/* eslint-disable react/prop-types */}
{(props) => {
return (
<>
@@ -111,7 +193,7 @@ const GDAPInviteWizard = () => {
Roles and group names
{props.values.gdapRoles.map((role, idx) => (
<>
- {role.RoleName == 'Company Administrator' && (
+ {role.RoleName === 'Company Administrator' && (
WARNING: The Company Administrator role will prevent GDAP
relationships from automatically extending. We recommend against using
@@ -134,17 +216,31 @@ const GDAPInviteWizard = () => {
}}
)}
- {postResults.isFetching && (
-
- Loading
-
- )}
- {postResults.isSuccess && (
-
- {postResults.data.Results.map((message, idx) => {
- return {message}
- })}
-
+ {(massResults.length >= 1 || loopRunning) && (
+ <>
+
+ {loopRunning ? (
+
+ Generating Invites
+
+ ) : (
+
+ Generating Invites
+
+
+ )}
+
+ {massResults.length}/{inviteCount}
+
+
+
+
+ >
)}
diff --git a/src/views/tenant/administration/GDAPRoleWizard.js b/src/views/tenant/administration/GDAPRoleWizard.jsx
similarity index 97%
rename from src/views/tenant/administration/GDAPRoleWizard.js
rename to src/views/tenant/administration/GDAPRoleWizard.jsx
index 83830e976cab..acaca41de84a 100644
--- a/src/views/tenant/administration/GDAPRoleWizard.js
+++ b/src/views/tenant/administration/GDAPRoleWizard.jsx
@@ -114,6 +114,7 @@ const GDAPRoleWizard = () => {
{!postResults.isSuccess && (
+ {/* eslint-disable react/prop-types */}
{(props) => {
return (
<>
@@ -167,8 +168,8 @@ const GDAPRoleWizard = () => {
return {message}
})}
-
- Start GDAP Migration
+
+ Create GDAP Invite
>
)}
diff --git a/src/views/tenant/administration/GDAPWizard.js b/src/views/tenant/administration/GDAPWizard.jsx
similarity index 96%
rename from src/views/tenant/administration/GDAPWizard.js
rename to src/views/tenant/administration/GDAPWizard.jsx
index 3c6147e7547d..d68a20c3df58 100644
--- a/src/views/tenant/administration/GDAPWizard.js
+++ b/src/views/tenant/administration/GDAPWizard.jsx
@@ -54,7 +54,12 @@ const GDAPWizard = () => {
The GDAP migration tool requires setup. Please check the documentation{' '}
-
+
here.
@@ -172,6 +177,7 @@ const GDAPWizard = () => {
{!postResults.isSuccess && (
+ {/* eslint-disable react/prop-types */}
{(props) => {
return (
<>
@@ -189,7 +195,7 @@ const GDAPWizard = () => {
Roles and group names
{props.values.gdapRoles.map((role, idx) => (
<>
- {role.RoleName == 'Company Administrator' && (
+ {role.RoleName === 'Company Administrator' && (
WARNING: The Company Administrator role will prevent GDAP
relationships from automatically extending. We recommend against using
diff --git a/src/views/tenant/administration/GeoIPLookup.js b/src/views/tenant/administration/GeoIPLookup.jsx
similarity index 64%
rename from src/views/tenant/administration/GeoIPLookup.js
rename to src/views/tenant/administration/GeoIPLookup.jsx
index dd1b21e215a4..339a93630a99 100644
--- a/src/views/tenant/administration/GeoIPLookup.js
+++ b/src/views/tenant/administration/GeoIPLookup.jsx
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react'
import {
CButton,
+ CCallout,
CCard,
CCardBody,
CCardHeader,
@@ -57,6 +58,18 @@ const GeoIPLookup = () => {
})
}
}, [execGraphRequest, tenant.defaultDomainName, query, ip])
+ const [execAddIp, iprequest] = useLazyGenericGetRequestQuery()
+
+ const addTrustedIP = (State) => {
+ execAddIp({
+ path: 'api/ExecAddTrustedIP',
+ params: {
+ IP: ip,
+ TenantFilter: tenant.defaultDomainName,
+ State: State,
+ },
+ })
+ }
return (
@@ -115,28 +128,71 @@ const GeoIPLookup = () => {
{ip}
- Range
+ AS
{graphrequest.isFetching && }
- {graphrequest.data?.startaddress} - {graphrequest.data?.endAddress}
+ {graphrequest.data?.as}
Owner
{graphrequest.isFetching && }
- {graphrequest.data?.OrgRef}
+ {graphrequest.data?.org}
- Subnet Name
+ ISP
{graphrequest.isFetching && }
- {graphrequest.data?.SubnetName}
+ {graphrequest.data?.isp}
-
+
Geo IP Location
{graphrequest.isFetching && }
- {graphrequest.data?.location?.countryCode} - {graphrequest.data?.location?.cityName}
+ {graphrequest.data?.country} - {graphrequest.data?.city}
+
+
+ Lat/Lon
+ {graphrequest.isFetching && }
+ {graphrequest.data?.lat} / {graphrequest.data?.lon}
+
+
+
+
+ Hosting
+ {graphrequest.isFetching && }
+ {graphrequest.data?.hosting ? 'Yes' : 'No'}
+
+
+ Mobile
+ {graphrequest.isFetching && }
+ {graphrequest.data?.mobile ? 'Yes' : 'No'}
+
+
+ Proxy or Anonimizer
+ {graphrequest.isFetching && }
+ {graphrequest.data?.proxy ? 'Yes' : 'No'}
+
+
+
+
+ addTrustedIP('Trusted')} className="me-3">
+ Add as trusted IP for selected tenant
+ {iprequest.isFetching && }
+
+ addTrustedIP('NotTrusted')}
+ >
+ Remove as trusted IP for selected tenant
+ {iprequest.isFetching && }
+
+ {iprequest.data && (
+
+ {iprequest.data?.results}
+
+ )}
)}
diff --git a/src/views/tenant/administration/GraphExplorer.js b/src/views/tenant/administration/GraphExplorer.js
deleted file mode 100644
index 4bb325b4f0ff..000000000000
--- a/src/views/tenant/administration/GraphExplorer.js
+++ /dev/null
@@ -1,249 +0,0 @@
-import React, { useEffect, useState } from 'react'
-import {
- CButton,
- CCard,
- CCardBody,
- CCardHeader,
- CCardTitle,
- CCol,
- CCollapse,
- CForm,
- CRow,
-} from '@coreui/react'
-import useQuery from 'src/hooks/useQuery'
-import { Field, Form, FormSpy } from 'react-final-form'
-import { RFFCFormCheck, RFFCFormInput, RFFCFormSelect } from 'src/components/forms'
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
-import { faChevronRight, faChevronDown, faSearch } from '@fortawesome/free-solid-svg-icons'
-import { CippTable } from 'src/components/tables'
-import { useSelector } from 'react-redux'
-import { useNavigate } from 'react-router-dom'
-import { CippPage } from 'src/components/layout/CippPage'
-import { useLazyGenericGetRequestQuery } from 'src/store/api/app'
-import { OnChange } from 'react-final-form-listeners'
-import { queryString } from 'src/helpers'
-import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat'
-
-const GraphExplorer = () => {
- let navigate = useNavigate()
- const tenant = useSelector((state) => state.app.currentTenant)
- let query = useQuery()
- const endpoint = query.get('endpoint')
- const disablePagination = query.get('disablePagination')
- const SearchNow = query.get('SearchNow')
- const [visibleA, setVisibleA] = useState(true)
- const handleSubmit = async (values) => {
- setVisibleA(false)
-
- const shippedValues = {
- tenantFilter: tenant.defaultDomainName,
- SearchNow: true,
- endpoint: encodeURIComponent(values.endpoint),
- random: (Math.random() + 1).toString(36).substring(7),
- }
- var queryString = Object.keys(shippedValues)
- .map((key) => key + '=' + shippedValues[key])
- .join('&')
-
- navigate(`?${queryString}`)
- }
- const [execGraphRequest, graphrequest] = useLazyGenericGetRequestQuery()
- const QueryColumns = { set: false, data: [] }
-
- if (graphrequest.isSuccess) {
- if (graphrequest.data.length === 0) {
- graphrequest.data = [{ data: 'No Data Found' }]
- }
-
- //set columns
-
- const flatObj = Object.keys(graphrequest.data[0]).flat(100)
- flatObj.map((value) =>
- QueryColumns.data.push({
- name: value,
- selector: (row) => row[`${value.toString()}`],
- sortable: true,
- exportSelector: value,
- cell: cellGenericFormatter(),
- }),
- )
- QueryColumns.set = true
- }
-
- useEffect(() => {
- execGraphRequest({
- path: 'api/execGraphRequest',
- params: {
- tenantFilter: tenant.defaultDomainName,
- endpoint: endpoint,
- disablePagination: disablePagination,
- },
- })
- }, [endpoint, execGraphRequest, tenant.defaultDomainName, query])
-
- const WhenFieldChanges = ({ field, set }) => (
-
- {(
- // No subscription. We only use Field to get to the change function
- { input: { onChange } },
- ) => (
-
- {({ form }) => (
-
- {(value) => {
- let template = value
- onChange(template)
- }}
-
- )}
-
- )}
-
- )
-
- return (
- <>
-
-
-
-
-
- Report Settings
- setVisibleA(!visibleA)}
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {!SearchNow && Execute a search to get started. }
- {graphrequest.isSuccess && QueryColumns.set && SearchNow && (
-
-
- Results
-
-
-
-
-
- )}
-
- >
- )
-}
-
-export default GraphExplorer
diff --git a/src/views/tenant/administration/GraphExplorer.jsx b/src/views/tenant/administration/GraphExplorer.jsx
new file mode 100644
index 000000000000..507fe3ebc5f9
--- /dev/null
+++ b/src/views/tenant/administration/GraphExplorer.jsx
@@ -0,0 +1,485 @@
+import React, { useEffect, useState, useRef } from 'react'
+import {
+ CAlert,
+ CButton,
+ CCallout,
+ CCard,
+ CCardBody,
+ CCardHeader,
+ CCardTitle,
+ CCol,
+ CCollapse,
+ CForm,
+ CRow,
+ CSpinner,
+ CTooltip,
+} from '@coreui/react'
+import useQuery from 'src/hooks/useQuery'
+import { Field, Form, FormSpy } from 'react-final-form'
+import { RFFCFormInput, RFFCFormSwitch, RFFSelectSearch } from 'src/components/forms'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faChevronRight, faChevronDown, faSearch } from '@fortawesome/free-solid-svg-icons'
+import { CippTable } from 'src/components/tables'
+import { useSelector } from 'react-redux'
+import { CippPage } from 'src/components/layout/CippPage'
+import {
+ useGenericGetRequestQuery,
+ useLazyGenericGetRequestQuery,
+ useLazyGenericPostRequestQuery,
+} from 'src/store/api/app'
+import { OnChange } from 'react-final-form-listeners'
+import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat'
+import PropTypes from 'prop-types'
+import { ModalService } from 'src/components/utilities'
+
+const GraphExplorer = () => {
+ const tenant = useSelector((state) => state.app.currentTenant)
+ let query = useQuery()
+ const [params, setParams] = useState()
+ const [alertVisible, setAlertVisible] = useState()
+ const [random, setRandom] = useState('')
+ const [random2, setRandom2] = useState('')
+ const [searchNow, setSearchNow] = useState(false)
+ const [visibleA, setVisibleA] = useState(true)
+ const handleSubmit = async (values) => {
+ setParams(values)
+ setRandom((Math.random() + 1).toString(36).substring(7))
+ setSearchNow(true)
+ }
+ const [execGraphRequest, graphrequest] = useLazyGenericGetRequestQuery()
+ const [execPostRequest, postResults] = useLazyGenericPostRequestQuery()
+ const {
+ data: customPresets = [],
+ isFetching: presetsIsFetching,
+ error: presetsError,
+ } = useGenericGetRequestQuery({ path: '/api/ListGraphExplorerPresets', params: { random2 } })
+ const QueryColumns = { set: false, data: [] }
+
+ if (graphrequest.isSuccess) {
+ if (graphrequest.data.Results.length === 0) {
+ graphrequest.data = [{ data: 'No Data Found' }]
+ }
+
+ //set columns
+ Object.keys(graphrequest.data.Results[0]).map((value) =>
+ QueryColumns.data.push({
+ name: value,
+ selector: (row) => row[`${value.toString()}`],
+ sortable: true,
+ exportSelector: value,
+ cell: cellGenericFormatter(),
+ }),
+ )
+ QueryColumns.set = true
+ }
+
+ const handleManagePreset = ({ values, action, message }) => {
+ var params = {
+ action: action,
+ values: values,
+ }
+ ModalService.confirm({
+ title: 'Confirm',
+ body: {message}
,
+ onConfirm: () => {
+ execPostRequest({ path: '/api/ExecGraphExplorerPreset', values: params }).then(() => {
+ setRandom2((Math.random() + 1).toString(36).substring(7))
+ setAlertVisible(true)
+ })
+ },
+ confirmLabel: action,
+ cancelLabel: 'Cancel',
+ })
+ }
+
+ const presets = [
+ {
+ name: 'All users with email addresses',
+ id: '6164e239-0c9a-4a27-9049-6250bf65a3e3',
+ params: { endpoint: '/users', $select: 'userprincipalname,mail,proxyAddresses', $filter: '' },
+ isBuiltin: true,
+ },
+ {
+ name: 'All Devices listing ID, Displayname, and registration status',
+ id: 'e7fdc49a-72a9-4a70-9dbf-a74152495d80',
+ params: {
+ endpoint: '/devices',
+ $select: 'deviceId,DisplayName,profileType,registrationDateTime,trustType',
+ $filter: '',
+ },
+ isBuiltin: true,
+ },
+ {
+ name: 'All contacts and their mail addresses',
+ id: 'f1844e3d-cb3e-4611-9bab-f5f42169bcd0',
+ params: {
+ endpoint: '/contacts',
+ $select: 'CompanyName,DisplayName,Mail,ProxyAddresses',
+ $filter: '',
+ },
+ isBuiltin: true,
+ },
+ {
+ name: 'Outlook Agents used in last 90 days',
+ id: 'eea3cacb-ca95-4998-bcd9-ff1815a7a493',
+ params: {
+ endpoint: `reports/getEmailAppUsageUserDetail(period='D90')`,
+ $format: 'application/json',
+ $filter: '',
+ $select: '',
+ isBuiltin: true,
+ },
+ },
+ {
+ name: 'Activated M365 Subscription installations',
+ id: 'ccbe5b0d-ff0d-4262-a789-ccbd8f8d52e1',
+ params: {
+ endpoint: '/reports/getOffice365ActivationsUserDetail',
+ $format: 'application/json',
+ $filter: '',
+ $select: '',
+ },
+ isBuiltin: true,
+ },
+ {
+ name: 'Applications signed in in last 30 days',
+ id: 'a9ec9f2d-c102-4b4f-9b9d-c2bc57155990',
+ params: {
+ endpoint: `reports/getAzureADApplicationSignInSummary(period='D30')`,
+ $filter: '',
+ $select: '',
+ },
+ isBuiltin: true,
+ },
+ {
+ name: 'User Registration Report',
+ id: 'a00d251d-2743-484a-b8bb-8601199ceb68',
+ params: {
+ endpoint: '/reports/authenticationMethods/userRegistrationDetails',
+ $filter: '',
+ $select: '',
+ },
+ isBuiltin: true,
+ },
+ {
+ name: 'All Global Admins',
+ id: 'c73df2bb-08fe-4fb2-b112-97006bdcf0a8',
+ params: {
+ endpoint: 'directoryRoles/roleTemplateId=62e90394-69f5-4237-9190-012177145e10/members',
+ $filter: '',
+ $select: '',
+ },
+ isBuiltin: true,
+ },
+ {
+ name: 'Multifactor Authentication Report for Admins',
+ id: 'ae7b3dc4-822b-46b9-aa0a-0305b4c33632',
+ params: {
+ endpoint: '/reports/authenticationMethods/userRegistrationDetails',
+ $filter: 'IsAdmin eq true',
+ $select: '',
+ },
+ isBuiltin: true,
+ },
+ {
+ name: 'Secure Score with Current Score and Max Score',
+ id: 'bd6665e8-02c1-4cbf-bd3c-d2a52f17c2cf',
+ params: {
+ endpoint: 'security/secureScores',
+ $top: 90,
+ $select: 'currentscore,maxscore,activeusercount,enabledservices',
+ $filter: '',
+ NoPagination: true,
+ },
+ isBuiltin: true,
+ },
+ ]
+
+ if (customPresets?.Results?.length > 0) {
+ presets.push({
+ name: 'âââââââââââââââ',
+ id: '',
+ props: {
+ isDisabled: true,
+ },
+ })
+ customPresets.Results.map((preset) => {
+ presets.push(preset)
+ })
+ }
+
+ useEffect(() => {
+ if (params?.endpoint) {
+ execGraphRequest({
+ path: 'api/ListGraphRequest',
+ params: {
+ ...params,
+ random: random,
+ },
+ })
+ }
+ }, [params, execGraphRequest, tenant.defaultDomainName, random])
+
+ const WhenFieldChanges = ({ field, set }) => (
+
+ {(
+ // No subscription. We only use Field to get to the change function
+ { input: { onChange } },
+ ) => (
+
+ {({ form }) => (
+
+ {(value) => {
+ if (value?.value) {
+ let preset = presets.filter(function (obj) {
+ return obj.id === value.value
+ })
+ if (preset[0]?.id !== '') {
+ if (preset[0]?.params[set]) {
+ onChange(preset[0]?.params[set])
+ } else {
+ onChange(preset[0][set])
+ }
+ }
+ }
+ }}
+
+ )}
+
+ )}
+
+ )
+ WhenFieldChanges.propTypes = {
+ field: PropTypes.node,
+ set: PropTypes.string,
+ }
+
+ return (
+ <>
+
+
+
+
+
+ Report Settings
+ setVisibleA(!visibleA)}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!searchNow && Execute a search to get started. }
+ {graphrequest.isSuccess && QueryColumns.set && searchNow && (
+
+
+ Results
+
+
+
+
+
+ )}
+
+ >
+ )
+}
+
+export default GraphExplorer
diff --git a/src/views/tenant/administration/ListAlertsQueue.js b/src/views/tenant/administration/ListAlertsQueue.js
deleted file mode 100644
index 720e55a2fcc3..000000000000
--- a/src/views/tenant/administration/ListAlertsQueue.js
+++ /dev/null
@@ -1,301 +0,0 @@
-import React from 'react'
-import { useSelector } from 'react-redux'
-import { CSpinner, CButton, CCallout, CRow } from '@coreui/react'
-import { faTrash } from '@fortawesome/free-solid-svg-icons'
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
-import { CippPageList } from 'src/components/layout'
-import { ModalService } from 'src/components/utilities'
-import { useLazyGenericGetRequestQuery } from 'src/store/api/app'
-import { cellBooleanFormatter } from 'src/components/tables'
-import { CellTip } from 'src/components/tables/CellGenericFormat'
-
-const ListAlertsQueue = () => {
- const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery()
- const Actions = (row, index, column) => {
- const handleDeleteStandard = (apiurl, message) => {
- ModalService.confirm({
- title: 'Confirm',
- body: {message}
,
- onConfirm: () => ExecuteGetRequest({ path: apiurl }),
- confirmLabel: 'Continue',
- cancelLabel: 'Cancel',
- })
- }
- return (
-
- handleDeleteStandard(
- `api/RemoveQueuedAlert?ID=${row.tenantId}`,
- 'Do you want to delete the queued alert?',
- )
- }
- >
-
-
- )
- }
-
- const ActionsWebhook = (row, index, column) => {
- const handleDeleteStandard = (apiurl, message) => {
- ModalService.confirm({
- title: 'Confirm',
- body: {message}
,
- onConfirm: () => ExecuteGetRequest({ path: apiurl }),
- confirmLabel: 'Continue',
- cancelLabel: 'Cancel',
- })
- }
- return (
-
- handleDeleteStandard(
- `api/RemoveWebhookAlert?CIPPID=${row.RowKey}&TenantFilter=${row.PartitionKey}`,
- 'Do you want to delete the queued alert?',
- )
- }
- >
-
-
- )
- }
- const webhookcolumns = [
- {
- name: 'Tenant',
- selector: (row) => row['PartitionKey'],
- sortable: true,
- exportSelector: 'PartitionKey',
- },
- {
- name: 'Expiration',
- selector: (row) => row['Expiration'],
- sortable: true,
- exportSelector: 'Expiration',
- cell: (row) => CellTip(row['Expiration']),
- },
- {
- name: 'Monitored Resource',
- selector: (row) => row['Resource'],
- sortable: true,
- exportSelector: 'Resource',
- cell: (row) => CellTip(row['Resource']),
- },
- {
- name: 'Monitored Actions',
- selector: (row) => row['Operations'],
- sortable: true,
- exportSelector: 'Operations',
- cell: (row) => CellTip(row['Operations']),
- },
- {
- name: 'Webhook URL',
- selector: (row) => row['WebhookNotificationUrl'],
- sortable: true,
- exportSelector: 'WebhookNotificationUrl',
- cell: (row) => CellTip(row['WebhookNotificationUrl']),
- },
- {
- name: 'Actions',
- cell: ActionsWebhook,
- },
- ]
-
- const columns = [
- {
- name: 'Tenant',
- selector: (row) => row['tenantName'],
- sortable: true,
- exportSelector: 'tenantName',
- },
- {
- name: 'Changed Admin Passwords',
- selector: (row) => row['AdminPassword'],
- sortable: true,
- exportSelector: 'AdminPassword',
- cell: cellBooleanFormatter(),
- },
- {
- name: 'Defender Malware Alerts',
- selector: (row) => row['DefenderMalware'],
- sortable: true,
- exportSelector: 'DefenderMalware',
- cell: cellBooleanFormatter(),
- },
- {
- name: 'MFA for Admins',
- selector: (row) => row['MFAAdmins'],
- sortable: true,
- exportSelector: 'MFAAdmins',
- cell: cellBooleanFormatter(),
- },
- {
- name: 'MFA for Users',
- selector: (row) => row['MFAAlertUsers'],
- sortable: true,
- exportSelector: 'MFAAlertUsers',
- cell: cellBooleanFormatter(),
- },
- {
- name: 'Changes to Admin Roles',
- selector: (row) => row['NewRole'],
- sortable: true,
- exportSelector: 'NewRole',
- cell: cellBooleanFormatter(),
- },
- {
- name: 'Exchange Mailbox size',
- selector: (row) => row['QuotaUsed'],
- sortable: true,
- exportSelector: 'QuotaUsed',
- cell: cellBooleanFormatter(),
- },
- {
- name: 'Unused Licenses',
- selector: (row) => row['UnusedLicenses'],
- sortable: true,
- exportSelector: 'UnusedLicenses',
- cell: cellBooleanFormatter(),
- },
- {
- name: 'Overused Licenses',
- selector: (row) => row['OverusedLicenses'],
- sortable: true,
- exportSelector: 'OverusedLicenses',
- cell: cellBooleanFormatter(),
- },
- {
- name: 'App Secret Expiry',
- selector: (row) => row['AppSecretExpiry'],
- sortable: true,
- exportSelector: 'AppSecretExpiry',
- cell: cellBooleanFormatter(),
- },
- {
- name: 'APN Cert Expiry',
- selector: (row) => row['ApnCertExpiry'],
- sortable: true,
- exportSelector: 'ApnCertExpiry',
- cell: cellBooleanFormatter(),
- },
- {
- name: 'VPP Token Expiry',
- selector: (row) => row['VppTokenExpiry'],
- sortable: true,
- exportSelector: 'VppTokenExpiry',
- cell: cellBooleanFormatter(),
- },
- {
- name: 'DEP Token Expiry',
- selector: (row) => row['DepTokenExpiry'],
- sortable: true,
- exportSelector: 'DepTokenExpiry',
- cell: cellBooleanFormatter(),
- },
- {
- name: 'No CA Config',
- selector: (row) => row['NoCAConfig'],
- sortable: true,
- exportSelector: 'NoCAConfig',
- cell: cellBooleanFormatter(),
- },
- {
- name: 'Sec Defaults Auto-Enable',
- selector: (row) => row['SecDefaultsUpsell'],
- sortable: true,
- exportSelector: 'SecDefaultsUpsell',
- cell: cellBooleanFormatter(),
- },
- {
- name: 'Sharepoint Quota',
- selector: (row) => row['SharepointQuota'],
- sortable: true,
- exportSelector: 'SharepointQuota',
- cell: cellBooleanFormatter(),
- },
- {
- name: 'Expiring Licenses',
- selector: (row) => row['ExpiringLicenses'],
- sortable: true,
- exportSelector: 'ExpiringLicenses',
- cell: cellBooleanFormatter(),
- },
- {
- name: 'Actions',
- cell: Actions,
- },
- ]
- const tenant = useSelector((state) => state.app.currentTenant)
-
- return (
-
- {getResults.isFetching && (
-
- Loading
-
- )}
- {getResults.isSuccess && {getResults.data?.Results} }
- {getResults.isError && (
- Could not connect to API: {getResults.error.message}
- )}
-
-
-
-
-
-
-
- )
-}
-
-export default ListAlertsQueue
diff --git a/src/views/tenant/administration/ListAlertsQueue.jsx b/src/views/tenant/administration/ListAlertsQueue.jsx
new file mode 100644
index 000000000000..b0800dfaa7bf
--- /dev/null
+++ b/src/views/tenant/administration/ListAlertsQueue.jsx
@@ -0,0 +1,284 @@
+import React, { useState } from 'react'
+import { CButton, CCallout, CCol, CForm, CRow, CSpinner, CTooltip } from '@coreui/react'
+import { useSelector } from 'react-redux'
+import { Field, Form } from 'react-final-form'
+import { RFFCFormSwitch } from 'src/components/forms'
+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 { CellTip } from 'src/components/tables/CellGenericFormat'
+import 'react-datepicker/dist/react-datepicker.css'
+import { CippActionsOffcanvas, ModalService, TenantSelector } from 'src/components/utilities'
+import CippCodeOffCanvas from 'src/components/utilities/CippCodeOffcanvas'
+import arrayMutators from 'final-form-arrays'
+const alertsList = [
+ { name: 'MFAAlertUsers', label: 'Alert on users without any form of MFA' },
+ { name: 'MFAAdmins', label: 'Alert on admins without any form of MFA' },
+ {
+ name: 'NoCAConfig',
+ label:
+ 'Alert on tenants without a Conditional Access policy, while having Conditional Access licensing available.',
+ },
+ { name: 'AdminPassword', label: 'Alert on changed admin Passwords' },
+ { name: 'QuotaUsed', label: 'Alert on 90% mailbox quota used' },
+ { name: 'SharePointQuota', label: 'Alert on 90% SharePoint quota used' },
+ { name: 'ExpiringLicenses', label: 'Alert on licenses expiring in 30 days' },
+ { name: 'SecDefaultsUpsell', label: 'Alert on Security Defaults automatic enablement' },
+ {
+ name: 'DefenderStatus',
+ label: 'Alert if Defender is not running (Tenant must be on-boarded in Lighthouse)',
+ },
+ {
+ name: 'DefenderMalware',
+ label: 'Alert on Defender Malware found (Tenant must be on-boarded in Lighthouse)',
+ },
+ { name: 'UnusedLicenses', label: 'Alert on unused licenses' },
+ { name: 'OverusedLicenses', label: 'Alert on overused licenses' },
+ { name: 'AppSecretExpiry', label: 'Alert on expiring application secrets' },
+ { name: 'ApnCertExpiry', label: 'Alert on expiring APN certificates' },
+ { name: 'VppTokenExpiry', label: 'Alert on expiring VPP tokens' },
+ { name: 'DepTokenExpiry', label: 'Alert on expiring DEP tokens' },
+]
+
+const ListClassicAlerts = () => {
+ const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery()
+
+ const Offcanvas = (row, rowIndex, formatExtraData) => {
+ const [ocVisible, setOCVisible] = useState(false)
+
+ const handleDeleteSchedule = (apiurl, message) => {
+ ModalService.confirm({
+ title: 'Confirm',
+ body: {message}
,
+ onConfirm: () =>
+ ExecuteGetRequest({ path: apiurl }).then((res) => {
+ setRefreshState(res.requestId)
+ }),
+ confirmLabel: 'Continue',
+ cancelLabel: 'Cancel',
+ })
+ }
+ let jsonResults
+ try {
+ jsonResults = JSON.parse(row)
+ } catch (error) {
+ jsonResults = row
+ }
+
+ return (
+ <>
+
+ setOCVisible(true)}>
+
+
+
+
+
+ handleDeleteSchedule(
+ `/api/RemoveQueuedAlert?&ID=${row.tenantId}`,
+ 'Do you want to delete the queued alert?',
+ )
+ }
+ size="sm"
+ variant="ghost"
+ color="danger"
+ >
+
+
+
+ ({
+ label: key,
+ value:
+ typeof row[key] === 'boolean' ? (
+ row[key] ? (
+
+ ) : (
+
+ )
+ ) : (
+ row[key]
+ ),
+ }))}
+ placement="end"
+ visible={ocVisible}
+ id={row.id}
+ hideFunction={() => setOCVisible(false)}
+ />
+ >
+ )
+ }
+
+ const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName)
+ const [refreshState, setRefreshState] = useState(false)
+ const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
+ const onSubmit = (values) => {
+ Object.keys(values).filter(function (x) {
+ if (values[x] === null) {
+ delete values[x]
+ }
+ return null
+ })
+ values['tenantFilter'] = tenantDomain
+ values['SetAlerts'] = true
+ genericPostRequest({ path: '/api/AddAlert', values: values }).then((res) => {
+ setRefreshState(res.requestId)
+ })
+ }
+ const { data: currentlySelectedAlerts = [], isLoading: isLoadingCurrentAlerts } =
+ useGenericGetRequestQuery({
+ path: `api/ListAlertsQueue?TenantFilter=${tenantDomain}&RefreshGuid=${refreshState}`,
+ })
+
+ const columns = [
+ {
+ name: 'Tenant Name',
+ selector: (row) => row['tenantName'],
+ sortable: true,
+ cell: (row) => CellTip(row['tenantName']),
+ exportSelector: 'tenantName',
+ },
+ {
+ name: 'Tenant ID',
+ selector: (row) => row['tenantId'],
+ sortable: true,
+ cell: (row) => CellTip(row['tenantId']),
+ exportSelector: 'tenantId',
+ },
+ {
+ name: 'Actions',
+ cell: Offcanvas,
+ maxWidth: '80px',
+ },
+ ]
+ const initialValues = currentlySelectedAlerts.filter((x) => x.tenantName === tenantDomain)[0]
+ const allTenantsAlert = currentlySelectedAlerts.find(
+ (tenant) => tenant.tenantName === 'AllTenants',
+ )
+ function getLabel(item) {
+ if (typeof allTenantsAlert === 'object' && allTenantsAlert !== null) {
+ if (allTenantsAlert[`${item}`]) {
+ return `* Enabled via All Tenants`
+ }
+ }
+ return ''
+ }
+
+ return (
+
+ <>
+
+
+
+
+
+
+
+
+
+ >
+
+ )
+}
+
+export default ListClassicAlerts
diff --git a/src/views/tenant/administration/ListAppConsentRequests.jsx b/src/views/tenant/administration/ListAppConsentRequests.jsx
new file mode 100644
index 000000000000..45f883eee0d3
--- /dev/null
+++ b/src/views/tenant/administration/ListAppConsentRequests.jsx
@@ -0,0 +1,161 @@
+/* eslint-disable import/no-unresolved */
+import React, { useState, useEffect } from 'react'
+import { useSelector } from 'react-redux'
+import { CButton } from '@coreui/react'
+import { faEllipsisV } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { CippPageList } from 'src/components/layout'
+import { cellDateFormatter, cellNullTextFormatter } from 'src/components/tables'
+import { CippActionsOffcanvas } from 'src/components/utilities'
+import { CellTip } from 'src/components/tables/CellGenericFormat'
+
+const AppConsentRequests = () => {
+ const [tenantColumnSet, setTenantColumn] = useState(true)
+ const tenant = useSelector((state) => state.app.currentTenant)
+ useEffect(() => {
+ if (tenant.defaultDomainName === 'AllTenants') {
+ setTenantColumn(false)
+ }
+ if (tenant.defaultDomainName !== 'AllTenants') {
+ setTenantColumn(true)
+ }
+ }, [tenant.defaultDomainName, tenantColumnSet])
+
+ const columns = [
+ {
+ name: 'Tenant',
+ selector: (row) => row['Tenant'],
+ sortable: true,
+ cell: (row) => CellTip(row['Tenant']),
+ exportSelector: 'Tenant',
+ omit: tenantColumnSet,
+ },
+ {
+ name: 'Retrieval Status',
+ selector: (row) => row['CippStatus'],
+ sortable: true,
+ cell: (row) => CellTip(row['CippStatus']),
+ exportSelector: 'CippStatus',
+ omit: tenantColumnSet,
+ },
+ {
+ name: 'Application Name',
+ selector: (row) => row.appDisplayName,
+ sortable: true,
+ exportSelector: 'appDisplayName',
+ },
+ {
+ name: 'Requester',
+ selector: (row) => row.requestUser,
+ sortable: true,
+ exportSelector: 'requestUser',
+ },
+ {
+ name: 'Reason',
+ selector: (row) => row.requestReason,
+ sortable: true,
+ exportSelector: 'requestReason',
+ },
+ {
+ name: 'Status',
+ selector: (row) => row.requestStatus,
+ sortable: true,
+ exportSelector: 'requestStatus',
+ },
+ {
+ name: 'Request Date',
+ selector: (row) => row.requestDate,
+ sortable: true,
+ exportSelector: 'requestDate',
+ cell: cellDateFormatter({ format: 'short' }),
+ },
+ {
+ name: 'Actions',
+ cell: Offcanvas,
+ maxWidth: '80px',
+ },
+ ]
+ return (
+
+
+
+ )
+}
+
+export default AppConsentRequests
+
+const Offcanvas = (row, rowIndex, formatExtraData) => {
+ const tenant = useSelector((state) => state.app.currentTenant)
+ const [ocVisible, setOCVisible] = useState(false)
+ const entraLink = `https://entra.microsoft.com/${tenant.defaultDomainName}/#view/Microsoft_AAD_IAM/StartboardApplicationsMenuBlade/~/AccessRequests`
+
+ return (
+ <>
+ setOCVisible(true)}>
+
+
+ setOCVisible(false)}
+ />
+ >
+ )
+}
diff --git a/src/views/tenant/administration/ListEnterpriseApps.js b/src/views/tenant/administration/ListEnterpriseApps.jsx
similarity index 92%
rename from src/views/tenant/administration/ListEnterpriseApps.js
rename to src/views/tenant/administration/ListEnterpriseApps.jsx
index d6ae08d59059..5b2385a6c0f1 100644
--- a/src/views/tenant/administration/ListEnterpriseApps.js
+++ b/src/views/tenant/administration/ListEnterpriseApps.jsx
@@ -1,11 +1,7 @@
-import { CButton } from '@coreui/react'
-import { faEllipsisV, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import React, { useState, useEffect } from 'react'
import { useSelector } from 'react-redux'
import { CippPageList } from 'src/components/layout'
import { cellDateFormatter, cellNullTextFormatter } from 'src/components/tables'
-import { CippActionsOffcanvas } from 'src/components/utilities'
import { cellLogoFormatter } from 'src/components/tables/CellLogo'
import { CellTip } from 'src/components/tables/CellGenericFormat'
@@ -19,7 +15,7 @@ const EnterpriseApplications = () => {
if (tenant.defaultDomainName !== 'AllTenants') {
setTenantColumn(true)
}
- }, [tenantColumnSet])
+ }, [tenant.defaultDomainName, tenantColumnSet])
const columns = [
{
diff --git a/src/views/tenant/administration/ListGDAPQueue.js b/src/views/tenant/administration/ListGDAPQueue.jsx
similarity index 100%
rename from src/views/tenant/administration/ListGDAPQueue.js
rename to src/views/tenant/administration/ListGDAPQueue.jsx
diff --git a/src/views/tenant/administration/ListGDAPRelationships.js b/src/views/tenant/administration/ListGDAPRelationships.jsx
similarity index 95%
rename from src/views/tenant/administration/ListGDAPRelationships.js
rename to src/views/tenant/administration/ListGDAPRelationships.jsx
index 6ee6a8cfa07c..7987176646db 100644
--- a/src/views/tenant/administration/ListGDAPRelationships.js
+++ b/src/views/tenant/administration/ListGDAPRelationships.jsx
@@ -10,11 +10,9 @@ import React, { useState } from 'react'
import { useSelector } from 'react-redux'
import { CippPageList } from 'src/components/layout'
import { cellDateFormatter, cellNullTextFormatter } from 'src/components/tables'
-import { CippActionsOffcanvas } from 'src/components/utilities'
+import { CippActionsOffcanvas, ModalService } from 'src/components/utilities'
import GDAPRoles from 'src/data/GDAPRoles'
import { useLazyGenericGetRequestQuery } from 'src/store/api/app'
-import { ModalService } from 'src/components/utilities'
-import { constants } from 'buffer'
import Skeleton from 'react-loading-skeleton'
const RefreshAction = () => {
@@ -89,6 +87,13 @@ const Actions = (row, rowIndex, formatExtraData) => {
title={'GDAP - ' + row?.customer?.displayName}
extendedInfo={extendedInfo}
actions={[
+ {
+ label: 'Start Onboarding',
+ color: 'primary',
+ link:
+ '/tenant/administration/tenant-onboarding-wizard?tableFilter=Complex: id eq ' +
+ row.id,
+ },
{
label: 'Enable automatic extension',
color: 'info',
diff --git a/src/views/tenant/administration/ListGDAPRoles.js b/src/views/tenant/administration/ListGDAPRoles.jsx
similarity index 100%
rename from src/views/tenant/administration/ListGDAPRoles.js
rename to src/views/tenant/administration/ListGDAPRoles.jsx
diff --git a/src/views/tenant/administration/ListLicences.js b/src/views/tenant/administration/ListLicences.jsx
similarity index 100%
rename from src/views/tenant/administration/ListLicences.js
rename to src/views/tenant/administration/ListLicences.jsx
diff --git a/src/views/tenant/administration/ListOauthApps.js b/src/views/tenant/administration/ListOauthApps.jsx
similarity index 83%
rename from src/views/tenant/administration/ListOauthApps.js
rename to src/views/tenant/administration/ListOauthApps.jsx
index fb45f6bf115a..8a3a2fd0a3d2 100644
--- a/src/views/tenant/administration/ListOauthApps.js
+++ b/src/views/tenant/administration/ListOauthApps.jsx
@@ -23,10 +23,17 @@ const columns = [
},
{
name: 'Application ID',
- selector: (row) => row['ID'],
+ selector: (row) => row['ApplicationID'],
sortable: true,
- cell: (row) => CellTip(row['ID']),
- exportSelector: 'ID',
+ cell: (row) => CellTip(row['ApplicationID']),
+ exportSelector: 'ApplicationID',
+ },
+ {
+ name: 'Object ID',
+ selector: (row) => row['ObjectID'],
+ sortable: true,
+ cell: (row) => CellTip(row['ObjectID']),
+ exportSelector: 'ObjectID',
},
{
name: 'Scope (Permissions)',
diff --git a/src/views/tenant/administration/ServiceHealth.js b/src/views/tenant/administration/ServiceHealth.jsx
similarity index 100%
rename from src/views/tenant/administration/ServiceHealth.js
rename to src/views/tenant/administration/ServiceHealth.jsx
diff --git a/src/views/tenant/administration/TenantLookup.js b/src/views/tenant/administration/TenantLookup.jsx
similarity index 97%
rename from src/views/tenant/administration/TenantLookup.js
rename to src/views/tenant/administration/TenantLookup.jsx
index 97f76156e70e..abb95f701571 100644
--- a/src/views/tenant/administration/TenantLookup.js
+++ b/src/views/tenant/administration/TenantLookup.jsx
@@ -147,7 +147,9 @@ const GraphExplorer = () => {
Domains
{graphrequest.isFetching && }
{graphrequest.data?.Domains &&
- graphrequest.data?.Domains.map((domainname) => {domainname} )}
+ graphrequest.data?.Domains.map((domainName, idx) => (
+ {domainName}
+ ))}
diff --git a/src/views/tenant/administration/TenantOffboardingWizard.jsx b/src/views/tenant/administration/TenantOffboardingWizard.jsx
new file mode 100644
index 000000000000..81749ba3221b
--- /dev/null
+++ b/src/views/tenant/administration/TenantOffboardingWizard.jsx
@@ -0,0 +1,259 @@
+import React 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'
+import { faExclamationTriangle, faTimes, faCheck } from '@fortawesome/free-solid-svg-icons'
+import { useSelector } from 'react-redux'
+import { CippWizard } from 'src/components/layout'
+import vendors1 from 'src/data/vendorTenantList'
+import PropTypes from 'prop-types'
+import { RFFCFormCheck, RFFCFormInput, RFFCFormSwitch, RFFSelectSearch } from 'src/components/forms'
+import { TenantSelector } from 'src/components/utilities'
+import { useGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app'
+
+const Error = ({ name }) => (
+
+ touched && error ? (
+
+
+ {error}
+
+ ) : null
+ }
+ />
+)
+
+Error.propTypes = {
+ name: PropTypes.string.isRequired,
+}
+
+const TenantOffboardingWizard = () => {
+ const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName)
+ const currentSettings = useSelector((state) => state.app)
+ const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
+
+ const filterParts = vendors1.map((vendor) => `appOwnerOrganizationId eq ${vendor.vendorTenantId}`)
+ const filter = filterParts.join(' or ')
+
+ const {
+ data: vendorApps = [],
+ vendorAppsIsFetching,
+ vendorAppsIsSuccess,
+ } = useGenericGetRequestQuery({
+ path: 'api/ListGraphRequest',
+ params: {
+ TenantFilter: tenantDomain,
+ Endpoint: 'servicePrincipals',
+ $filter: filter,
+ $select: 'appId,displayName,appOwnerOrganizationId',
+ $count: true,
+ },
+ })
+
+ const handleSubmit = async (values) => {
+ const shippedValues = {
+ TenantFilter: tenantDomain,
+ RemoveVendorApps: values.vendorApplications ? values.vendorApplications : '',
+ RemoveCSPGuestUsers: values.RemoveCSPGuestUsers ? values.RemoveCSPGuestUsers : '',
+ RemoveCSPnotificationContacts: values.RemoveCSPnotificationContacts
+ ? values.RemoveCSPnotificationContacts
+ : '',
+ RemoveMultitenantCSPApps: values.RemoveMultitenantCSPApps
+ ? values.RemoveMultitenantCSPApps
+ : '',
+ TerminateGDAP: values.TerminateGDAP ? values.TerminateGDAP : '',
+ TerminateContract: values.TerminateContract ? values.TerminateContract : '',
+ }
+
+ //alert(JSON.stringify(values, null, 2))
+ genericPostRequest({ path: '/api/ExecOffboardTenant', values: shippedValues })
+ }
+
+ return (
+
+
+
+ Step 1
+ Choose a tenant
+
+
+ {(props) => }
+
+
+
+
+ Step 2
+ Choose tenant offboarding options
+
+
+
+
+
+ {vendorAppsIsFetching && }
+ {!vendorAppsIsFetching && (
+ ({
+ value: vendor.appId,
+ name: vendor.displayName,
+ }))}
+ />
+ )}
+
+
+
+
+
+
+
+ These actions will terminate all delegated access to the customer tenant!
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Step 3
+ Confirm and apply
+
+
+
+ {postResults.isFetching && (
+
+ Loading
+
+ )}
+ {postResults.isSuccess && (
+ <>
+
+ {postResults.data.Results.map((message, idx) => {
+ return {message}
+ })}
+
+
+ {postResults.data.Errors.map((message, idx) => {
+ return {message}
+ })}
+
+ >
+ )}
+ {!postResults.isSuccess && (
+
+ {/* eslint-disable react/prop-types */}
+ {(props) => (
+ <>
+
+
+
+
+ Selected Tenant:
+ {tenantDomain}
+
+
+
+
+
+
+
+
+
+ Remove vendor applications
+
+
+
+ Remove all notification contacts originating from the CSP tenant
+ (technical,security,marketing notifications)
+
+
+
+ Remove all guest users originating from the CSP tenant
+
+
+
+ Remove all multitenant applications originating from CSP tenant
+
+
+
+ Terminate all active GDAP relationships
+
+
+
+ Terminate contract relationship
+
+
+
+
+
+
+ >
+ )}
+
+ )}
+
+
+
+
+ )
+}
+
+export default TenantOffboardingWizard
diff --git a/src/views/tenant/administration/TenantOnboardingWizard.jsx b/src/views/tenant/administration/TenantOnboardingWizard.jsx
new file mode 100644
index 000000000000..bdd301663507
--- /dev/null
+++ b/src/views/tenant/administration/TenantOnboardingWizard.jsx
@@ -0,0 +1,434 @@
+import React, { useState, useRef, useEffect } from 'react'
+import {
+ CAccordion,
+ CAccordionBody,
+ CAccordionHeader,
+ CAccordionItem,
+ CButton,
+ CCallout,
+ CCol,
+ CRow,
+ CSpinner,
+} from '@coreui/react'
+import { Field, FormSpy } from 'react-final-form'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faExclamationTriangle, faTimes, faCheck } from '@fortawesome/free-solid-svg-icons'
+import { useSelector } from 'react-redux'
+import { CippWizard } from 'src/components/layout'
+import PropTypes from 'prop-types'
+import { RFFCFormCheck, RFFCFormInput, RFFCFormSwitch, RFFSelectSearch } from 'src/components/forms'
+import { CippCodeBlock, TenantSelector } from 'src/components/utilities'
+import { useLazyGenericPostRequestQuery } from 'src/store/api/app'
+import {
+ CellDate,
+ WizardTableField,
+ cellDateFormatter,
+ cellNullTextFormatter,
+} from 'src/components/tables'
+import ReactTimeAgo from 'react-time-ago'
+import { TableModalButton, TitleButton } from 'src/components/buttons'
+
+const Error = ({ name }) => (
+
+ touched && error ? (
+
+
+ {error}
+
+ ) : null
+ }
+ />
+)
+
+Error.propTypes = {
+ name: PropTypes.string.isRequired,
+}
+
+function useInterval(callback, delay, state) {
+ const savedCallback = useRef()
+
+ // Remember the latest callback.
+ useEffect(() => {
+ savedCallback.current = callback
+ })
+
+ // Set up the interval.
+ useEffect(() => {
+ function tick() {
+ savedCallback.current()
+ }
+
+ if (delay !== null) {
+ let id = setInterval(tick, delay)
+ return () => clearInterval(id)
+ }
+ }, [delay, state])
+}
+
+const RelationshipOnboarding = ({ relationship, gdapRoles, autoMapRoles, addMissingGroups }) => {
+ const [relationshipReady, setRelationshipReady] = useState(false)
+ const [refreshGuid, setRefreshGuid] = useState(false)
+ const [getOnboardingStatus, onboardingStatus] = useLazyGenericPostRequestQuery()
+ var headerIcon = relationshipReady ? 'check-circle' : 'question-circle'
+
+ useInterval(
+ async () => {
+ if (onboardingStatus.data?.Status == 'running' || onboardingStatus.data?.Status == 'queued') {
+ getOnboardingStatus({
+ path: '/api/ExecOnboardTenant',
+ values: { id: relationship.id },
+ })
+ }
+ },
+ 5000,
+ onboardingStatus.data,
+ )
+
+ return (
+
+
+ {onboardingStatus?.data?.Status == 'running' ? (
+
+ ) : (
+
+ )}
+ Onboarding Relationship: {}
+ {relationship.displayName}
+
+
+
+ {(relationship?.customer?.displayName ||
+ onboardingStatus?.data?.Relationship?.customer?.displayName) && (
+
+ Customer
+ {onboardingStatus?.data?.Relationship?.customer?.displayName
+ ? onboardingStatus?.data?.Relationship?.customer?.displayName
+ : relationship.customer.displayName}
+
+ )}
+ {onboardingStatus?.data?.Timestamp && (
+
+ Last Updated
+
+
+ )}
+
+ Relationship Status
+ {relationship.status}
+
+
+ Creation Date
+
+
+ {relationship.status == 'approvalPending' &&
+ onboardingStatus?.data?.Relationship?.status != 'active' && (
+
+ Invite URL
+
+
+ )}
+
+ {onboardingStatus.isUninitialized &&
+ getOnboardingStatus({
+ path: '/api/ExecOnboardTenant',
+ values: { id: relationship.id, gdapRoles, autoMapRoles, addMissingGroups },
+ })}
+ {onboardingStatus.isSuccess && (
+ <>
+ {onboardingStatus.data?.Status != 'running' &&
+ onboardingStatus.data?.Status != 'queued' && (
+
+ getOnboardingStatus({
+ path: '/api/ExecOnboardTenant?Retry=True',
+ values: { id: relationship.id, gdapRoles, autoMapRoles, addMissingGroups },
+ })
+ }
+ className="mb-3 me-2"
+ >
+ Retry
+
+ )}
+ {onboardingStatus.data?.Logs && (
+
+ )}
+
+ {onboardingStatus.data?.OnboardingSteps?.map((step, idx) => (
+
+
+ {step.Status == 'running' ? (
+
+ ) : (
+
+ )}{' '}
+ {step.Title}
+
+
+ {step.Message}
+
+
+ ))}
+ >
+ )}
+
+
+ )
+}
+
+const TenantOnboardingWizard = () => {
+ const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName)
+ const currentSettings = useSelector((state) => state.app)
+ const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
+ const requiredArray = (value) => (value && value.length !== 0 ? undefined : 'Required')
+
+ const handleSubmit = async (values) => {}
+ const columns = [
+ {
+ name: 'Tenant',
+ selector: (row) => row.customer?.displayName,
+ sortable: true,
+ exportSelector: 'customer/displayName',
+ cell: cellNullTextFormatter(),
+ },
+ {
+ name: 'Relationship Name',
+ selector: (row) => row['displayName'],
+ sortable: true,
+ exportSelector: 'displayName',
+ },
+ {
+ name: 'Status',
+ selector: (row) => row['status'],
+ sortable: true,
+ exportSelector: 'status',
+ },
+ {
+ name: 'Created',
+ selector: (row) => row['createdDateTime'],
+ sortable: true,
+ exportSelector: 'createdDateTime',
+ cell: cellDateFormatter({ format: 'short' }),
+ },
+ {
+ name: 'Activated',
+ selector: (row) => row['activatedDateTime'],
+ sortable: true,
+ exportSelector: 'activatedDateTime',
+ cell: cellDateFormatter({ format: 'short' }),
+ },
+ {
+ name: 'End',
+ selector: (row) => row['endDateTime'],
+ sortable: true,
+ 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: 'Includes CA Role',
+ selector: (row) => row?.accessDetails,
+ sortable: true,
+ cell: (row) =>
+ row?.accessDetails?.unifiedRoles?.filter(
+ (e) => e.roleDefinitionId === '62e90394-69f5-4237-9190-012177145e10',
+ ).length > 0
+ ? 'Yes'
+ : 'No',
+ },
+ ]
+ return (
+
+
+
+ Step 1
+ Choose a relationship
+
+
+
+
+
+
+ {(props) => (
+ <>
+
+
+ >
+ )}
+
+
+
+
+
+ Step 2
+ Tenant Onboarding Options
+
+
+
+
+
+ {/* eslint-disable react/prop-types */}
+ {(props) => {
+ return (
+ <>
+ {(props.values.autoMapRoles || props.values.addMissingGroups) && (
+
+ {(props) => (
+ <>
+ row['RoleName'],
+ sortable: true,
+ exportselector: 'Name',
+ },
+ {
+ name: 'Group',
+ selector: (row) => row['GroupName'],
+ sortable: true,
+ },
+ ]}
+ fieldProps={props}
+ />
+
+ >
+ )}
+
+ )}
+ >
+ )
+ }}
+
+
+
+
+
+ Step 3
+ Tenant Onboarding
+
+
+
+
+ {/* eslint-disable react/prop-types */}
+ {(props) => {
+ return (
+ <>
+
+
+
+ Onboarding Status
+
+ {props.values.selectedRelationships.map((relationship, idx) => (
+
+ ))}
+
+
+
+ >
+ )
+ }}
+
+
+
+
+
+ )
+}
+
+export default TenantOnboardingWizard
diff --git a/src/views/tenant/administration/Tenants.js b/src/views/tenant/administration/Tenants.jsx
similarity index 100%
rename from src/views/tenant/administration/Tenants.js
rename to src/views/tenant/administration/Tenants.jsx
diff --git a/src/views/tenant/conditional/AddCATemplate.js b/src/views/tenant/conditional/AddCATemplate.jsx
similarity index 100%
rename from src/views/tenant/conditional/AddCATemplate.js
rename to src/views/tenant/conditional/AddCATemplate.jsx
diff --git a/src/views/tenant/conditional/ConditionalAccess.js b/src/views/tenant/conditional/ConditionalAccess.jsx
similarity index 96%
rename from src/views/tenant/conditional/ConditionalAccess.js
rename to src/views/tenant/conditional/ConditionalAccess.jsx
index 54df028702e6..0684781a6928 100644
--- a/src/views/tenant/conditional/ConditionalAccess.js
+++ b/src/views/tenant/conditional/ConditionalAccess.jsx
@@ -13,6 +13,8 @@ import { useSelector } from 'react-redux'
import { CippPageList } from 'src/components/layout'
import { CippActionsOffcanvas } from 'src/components/utilities'
import { cellDateFormatter, CellTip } from 'src/components/tables'
+import { TitleButton } from 'src/components/buttons'
+
function DateNotNull(date) {
if (date === null || date === undefined || date === '' || date === 'undefined') {
return ' '
@@ -217,6 +219,14 @@ const ConditionalAccessList = () => {
return (
+
+ >
+ }
tenantSelector={false}
datatable={{
reportName: `${tenant?.defaultDomainName}-ConditionalAccess-List`,
diff --git a/src/views/tenant/conditional/DeployCA.js b/src/views/tenant/conditional/DeployCA.jsx
similarity index 98%
rename from src/views/tenant/conditional/DeployCA.js
rename to src/views/tenant/conditional/DeployCA.jsx
index edcda312215f..f03618ba0bce 100644
--- a/src/views/tenant/conditional/DeployCA.js
+++ b/src/views/tenant/conditional/DeployCA.jsx
@@ -64,6 +64,11 @@ const AddPolicy = () => {
)
+ WhenFieldChanges.propTypes = {
+ field: PropTypes.node,
+ set: PropTypes.string,
+ }
+
const formValues = {
TemplateType: 'Admin',
}
@@ -180,6 +185,7 @@ const AddPolicy = () => {
{!postResults.isSuccess && (
+ {/* eslint-disable react/prop-types */}
{(props) => {
return (
<>
diff --git a/src/views/tenant/conditional/DeployNamedLocation.js b/src/views/tenant/conditional/DeployNamedLocation.jsx
similarity index 100%
rename from src/views/tenant/conditional/DeployNamedLocation.js
rename to src/views/tenant/conditional/DeployNamedLocation.jsx
diff --git a/src/views/tenant/conditional/DeployVacation.jsx b/src/views/tenant/conditional/DeployVacation.jsx
new file mode 100644
index 000000000000..8f1fb21b7281
--- /dev/null
+++ b/src/views/tenant/conditional/DeployVacation.jsx
@@ -0,0 +1,184 @@
+import React, { useState } from 'react'
+import { CButton, CCallout, CCol, CForm, CRow, CSpinner, CTooltip } from '@coreui/react'
+import { useSelector } from 'react-redux'
+import { Field, Form } from 'react-final-form'
+import { Condition, RFFCFormSwitch, RFFSelectSearch } from 'src/components/forms'
+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 { CellTip } from 'src/components/tables/CellGenericFormat'
+import 'react-datepicker/dist/react-datepicker.css'
+import { CippActionsOffcanvas, ModalService, TenantSelector } from 'src/components/utilities'
+import arrayMutators from 'final-form-arrays'
+import DatePicker from 'react-datepicker'
+import 'react-datepicker/dist/react-datepicker.css'
+import { useListUsersQuery } from 'src/store/api/users'
+import { useListConditionalAccessPoliciesQuery } from 'src/store/api/tenants'
+
+const ListClassicAlerts = () => {
+ const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery()
+ const currentDate = new Date()
+ const [startDate, setStartDate] = useState(currentDate)
+ const [endDate, setEndDate] = useState(currentDate)
+
+ const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName)
+ const [refreshState, setRefreshState] = useState(false)
+ const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
+
+ const onSubmit = (values) => {
+ const startTime = Math.floor(startDate.getTime() / 1000)
+ const endTime = Math.floor(endDate.getTime() / 1000)
+ const shippedValues = {
+ tenantFilter: tenantDomain,
+ UserId: values.UserId?.value,
+ PolicyId: values.PolicyId?.value,
+ StartDate: startTime,
+ EndDate: endTime,
+ vacation: true,
+ }
+ genericPostRequest({ path: '/api/ExecCAExclusion', values: shippedValues }).then((res) => {
+ setRefreshState(res.requestId)
+ })
+ }
+
+ const {
+ data: users = [],
+ isFetching: usersIsFetching,
+ error: usersError,
+ } = useListUsersQuery({ tenantDomain })
+
+ const {
+ data: caPolicies = [],
+ isFetching: caIsFetching,
+ error: caError,
+ } = useListConditionalAccessPoliciesQuery({ domain: tenantDomain })
+
+ return (
+
+ <>
+
+
+
+
+
+
+ >
+
+ )
+}
+
+export default ListClassicAlerts
diff --git a/src/views/tenant/conditional/ListCATemplates.js b/src/views/tenant/conditional/ListCATemplates.jsx
similarity index 91%
rename from src/views/tenant/conditional/ListCATemplates.js
rename to src/views/tenant/conditional/ListCATemplates.jsx
index 835ed3fef2ff..555a595c3ddb 100644
--- a/src/views/tenant/conditional/ListCATemplates.js
+++ b/src/views/tenant/conditional/ListCATemplates.jsx
@@ -1,7 +1,6 @@
import React, { useState } from 'react'
import { useSelector } from 'react-redux'
-import { CippCodeBlock, CippOffcanvas } from 'src/components/utilities'
-import { CippDatatable } from 'src/components/tables'
+import { CippDatatable, CellTip } from 'src/components/tables'
import {
CCardBody,
CButton,
@@ -15,9 +14,7 @@ import { faEye, faTrash } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { useLazyGenericGetRequestQuery } from 'src/store/api/app'
import { CippPage } from 'src/components/layout'
-import { ModalService } from 'src/components/utilities'
-import { CellTip } from 'src/components/tables'
-import CippCodeOffCanvas from 'src/components/utilities/CippCodeOffcanvas'
+import { ModalService, CippCodeOffCanvas } from 'src/components/utilities'
//todo: expandable with RAWJson property.
diff --git a/src/views/tenant/conditional/NamedLocations.js b/src/views/tenant/conditional/NamedLocations.jsx
similarity index 86%
rename from src/views/tenant/conditional/NamedLocations.js
rename to src/views/tenant/conditional/NamedLocations.jsx
index 990301199ba2..b7f1ebb0f68c 100644
--- a/src/views/tenant/conditional/NamedLocations.js
+++ b/src/views/tenant/conditional/NamedLocations.jsx
@@ -13,6 +13,8 @@ import { useSelector } from 'react-redux'
import { CippPageList } from 'src/components/layout'
import { CippActionsOffcanvas } from 'src/components/utilities'
import { cellBooleanFormatter, cellDateFormatter, CellTip } from 'src/components/tables'
+import { TitleButton } from 'src/components/buttons'
+
function DateNotNull(date) {
if (date === null || date === undefined || date === '' || date === 'undefined') {
return ' '
@@ -67,7 +69,15 @@ const NamedLocationsList = () => {
return (
+
+ >
+ }
tenantSelector={false}
datatable={{
reportName: `${tenant?.defaultDomainName}-ConditionalAccess-List`,
diff --git a/src/views/tenant/standards/ApplyStandard.js b/src/views/tenant/standards/ApplyStandard.js
deleted file mode 100644
index 2f84f99607ea..000000000000
--- a/src/views/tenant/standards/ApplyStandard.js
+++ /dev/null
@@ -1,565 +0,0 @@
-import React from 'react'
-import { CCallout, CCol, CRow, CSpinner } from '@coreui/react'
-import { Field, FormSpy } from 'react-final-form'
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
-import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'
-import { CippWizard } from 'src/components/layout'
-import { WizardTableField } from 'src/components/tables'
-import PropTypes from 'prop-types'
-import {
- RFFCFormSwitch,
- Condition,
- RFFCFormInput,
- RFFCFormSelect,
- RFFSelectSearch,
-} from 'src/components/forms'
-import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app'
-import allStandardsList from 'src/data/standards'
-
-const Error = ({ name }) => (
-
- touched && error ? (
-
-
- {error}
-
- ) : null
- }
- />
-)
-
-Error.propTypes = {
- name: PropTypes.string.isRequired,
-}
-function getDeepKeys(obj) {
- return Object.keys(obj)
- .filter((key) => obj[key] instanceof Object)
- .map((key) => getDeepKeys(obj[key]).map((k) => `${key}.${k}`))
- .reduce((x, y) => x.concat(y), Object.keys(obj))
-}
-const ApplyStandard = () => {
- const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
-
- const [intuneGetRequest, intuneTemplates] = useLazyGenericGetRequestQuery()
- const [transportGetRequest, transportTemplates] = useLazyGenericGetRequestQuery()
- const [exConnectorGetRequest, exConnectorTemplates] = useLazyGenericGetRequestQuery()
- const [caGetRequest, caTemplates] = useLazyGenericGetRequestQuery()
- const [groupGetRequest, groupTemplates] = useLazyGenericGetRequestQuery()
-
- const handleSubmit = async (values) => {
- // @todo: clean this up api sided so we don't need to perform weird tricks.
- Object.keys(values.standards).filter(function (x) {
- if (values.standards[x] === false) {
- delete values.standards[x]
- }
- return null
- })
-
- values.selectedTenants.map(
- (tenant) =>
- (values.standards[`Select_${tenant.defaultDomainName}`] = tenant.defaultDomainName),
- )
- //filter on only objects that are 'true'
- genericPostRequest({ path: '/api/AddStandardsDeploy', values: values.standards })
- }
-
- const formValues = {
- selectedTenants: [],
- standards: {},
- }
-
- return (
-
-
-
- Ensure you read{' '}
-
- the documentation fully
- {' '}
- before proceeding with this wizard. Some of the changes cannot be reverted by CIPP.
-
-
- Step 1
- Choose a tenant
-
-
-
- {(props) => (
- row['displayName'],
- sortable: true,
- exportselector: 'displayName',
- },
- {
- name: 'Default Domain Name',
- selector: (row) => row['defaultDomainName'],
- sortable: true,
- exportselector: 'mail',
- },
- ]}
- fieldProps={props}
- />
- )}
-
-
-
-
-
-
- Step 2
- Select Standards
-
-
-
-
- {allStandardsList
- .filter((obj) => obj.cat === 'Global')
- .map((item, key) => (
- <>
-
-
- {item.addedComponent && (
-
- {item.addedComponent.type === 'Select' ? (
-
- ) : (
-
- )}
-
- )}
-
- >
- ))}
-
-
-
-
-
-
- Step 3
- Select Standards
-
-
-
-
- {allStandardsList
- .filter((obj) => obj.cat === 'AAD')
- .map((item, key) => (
- <>
-
-
- {item.addedComponent && (
-
- {item.addedComponent.type === 'Select' ? (
-
- ) : (
-
- )}
-
- )}
-
- >
- ))}
-
-
-
-
-
-
- Step 4
- Select Standards
-
-
-
-
- {allStandardsList
- .filter((obj) => obj.cat === 'Exchange')
- .map((item, key) => (
- <>
-
-
- {item.addedComponent && (
-
- {item.addedComponent.type === 'Select' ? (
-
- ) : (
-
- )}
-
- )}
-
- >
- ))}
-
-
-
-
-
-
- Step 5
- Select Standards
-
-
-
-
- {allStandardsList
- .filter((obj) => obj.cat === 'Intune')
- .map((item, key) => (
- <>
-
-
- {item.addedComponent && (
-
- {item.addedComponent.type === 'Select' ? (
-
- ) : (
-
- )}
-
- )}
-
- >
- ))}
-
-
-
-
-
-
- Step 5
- Select Standards
-
-
-
-
- {allStandardsList
- .filter((obj) => obj.cat === 'SharePoint')
- .map((item, key) => (
- <>
-
-
- {item.addedComponent && (
-
- {item.addedComponent.type === 'Select' ? (
-
- ) : (
-
- )}
-
- )}
-
- >
- ))}
-
-
-
-
-
-
- Step 7
- Select Default Templates to apply
-
-
-
- Attention: Selected options below will run every 3 hours and overwrite any previously set
- policy by the same name. This will keep the policy exactly in the state as defined by the
- template.
-
-
-
-
-
-
- {intuneTemplates.isUninitialized &&
- intuneGetRequest({ path: 'api/ListIntuneTemplates' })}
- {intuneTemplates.isSuccess && (
- ({
- value: template.GUID,
- name: template.Displayname,
- }))}
- placeholder="Select a template"
- label="Choose your Intune templates to apply"
- />
- )}
-
-
-
-
-
- {transportTemplates.isUninitialized &&
- transportGetRequest({ path: 'api/ListTransportRulesTemplates' })}
- {transportTemplates.isSuccess && (
- ({
- value: template.GUID,
- name: template.name,
- }))}
- placeholder="Select a template"
- label="Choose your Transport Rule templates to apply"
- />
- )}
-
-
-
-
-
- {caTemplates.isUninitialized && caGetRequest({ path: 'api/ListCAtemplates' })}
- {caTemplates.isSuccess && (
- ({
- value: template.GUID,
- name: template.displayName,
- }))}
- placeholder="Select a template"
- label="Choose your Conditional Access templates to apply"
- />
- )}
-
-
-
-
-
- {exConnectorTemplates.isUninitialized &&
- exConnectorGetRequest({ path: 'api/ListExConnectorTemplates' })}
- {exConnectorTemplates.isSuccess && (
- ({
- value: template.GUID,
- name: template.name,
- }))}
- placeholder="Select a template"
- label="Choose your Exchange Connector templates to apply"
- />
- )}
-
-
-
-
-
- {groupTemplates.isUninitialized &&
- groupGetRequest({ path: 'api/ListGroupTemplates' })}
- {groupTemplates.isSuccess && (
- ({
- value: template.GUID,
- name: template.Displayname,
- }))}
- placeholder="Select a template"
- label="Choose your Group templates to apply"
- />
- )}
-
-
-
-
-
-
-
-
- Step 6
- Confirm and apply
-
-
- {!postResults.isSuccess && (
-
-
-
- WARNING! Setting a standard will make changes to your tenants and set these standards
- on every 365 tenant you select. If you want to review only, please use the Best
- Practice Analyser.
-
-
- )}
- {postResults.isFetching && (
-
- Loading
-
- )}
- {!postResults.isSuccess && (
-
- {(props) => (
- <>
-
-
- Selected Tenants
-
- {props.values.selectedTenants.map((tenant, idx) => (
-
- {tenant.displayName}- {tenant.defaultDomainName}
-
- ))}
-
- Selected Standards
-
- {getDeepKeys(props.values.standards)
- .reduce((acc, key) => {
- const existingItem = allStandardsList.find((obj) =>
- obj.name.includes(key),
- )
- if (
- existingItem &&
- !acc.find((item) => item.name === existingItem.name)
- ) {
- acc.push(existingItem)
- }
- return acc
- }, [])
- .map((item, idx) => (
- {item.label}
- ))}
-
-
-
-
- >
- )}
-
- )}
- {postResults.isSuccess && {postResults.data.Results} }
-
-
-
- )
-}
-
-export default ApplyStandard
diff --git a/src/views/tenant/standards/BPAReportBuilder.js b/src/views/tenant/standards/BPAReportBuilder.jsx
similarity index 91%
rename from src/views/tenant/standards/BPAReportBuilder.js
rename to src/views/tenant/standards/BPAReportBuilder.jsx
index b649a86556d6..1a247f7bff68 100644
--- a/src/views/tenant/standards/BPAReportBuilder.js
+++ b/src/views/tenant/standards/BPAReportBuilder.jsx
@@ -1,10 +1,9 @@
import React, { useState, useEffect, useRef } from 'react'
-import { CippPage } from 'src/components/layout'
+import { CippPage, CippContentCard } from 'src/components/layout'
import BPAReportSchema from 'src/data/BPAReport.schema.v1'
import BPAReportUISchema from 'src/data/BPAReport.uischema.v1'
import validator from '@rjsf/validator-ajv8'
import Form from '@rjsf/bootstrap-4'
-import { CippContentCard } from 'src/components/layout'
import Editor from '@monaco-editor/react'
import { useSelector } from 'react-redux'
import useQuery from 'src/hooks/useQuery'
@@ -28,6 +27,7 @@ import {
import { useGenericGetRequestQuery } from 'src/store/api/app'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import CopyToClipboard from 'react-copy-to-clipboard'
+import PropTypes from 'prop-types'
const CippTextWidget = (props) => {
return (
@@ -39,6 +39,16 @@ const CippTextWidget = (props) => {
/>
)
}
+CippTextWidget.propTypes = {
+ value: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.arrayOf(PropTypes.string),
+ PropTypes.number,
+ ]),
+ required: PropTypes.bool,
+ onChange: PropTypes.func,
+}
+
const CippSelectWidget = (props) => {
const options = props?.options.length > 0 ? props.options : props.options.enumOptions
return (
@@ -56,6 +66,17 @@ const CippSelectWidget = (props) => {
)
}
+CippSelectWidget.propTypes = {
+ options: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
+ onChange: PropTypes.func,
+ value: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.arrayOf(PropTypes.string),
+ PropTypes.number,
+ ]),
+ required: PropTypes.bool,
+}
+
const CippCheckboxWidget = (props) => {
return (
{
/>
)
}
+
+CippCheckboxWidget.propTypes = {
+ disabled: PropTypes.bool,
+ name: PropTypes.string,
+ label: PropTypes.string,
+ value: PropTypes.bool,
+ onChange: PropTypes.func,
+}
+
const CippWidgets = {
TextWidget: CippTextWidget,
SelectWidget: CippSelectWidget,
@@ -99,6 +129,7 @@ const BPAReportBuilder = () => {
setFormData({})
}
}
+
const handleSubmit = async (event) => {
event.preventDefault()
var reportTemplate = event.target.form[0].value
@@ -250,7 +281,7 @@ const BPAReportBuilder = () => {
defaultLanguage="json"
value={JSON.stringify(formData, null, 2)}
onChange={handleEditorChange}
- theme={currentTheme == 'cyberdrain' ? 'vs-light' : 'vs-dark'}
+ theme={currentTheme === 'cyberdrain' ? 'vs-light' : 'vs-dark'}
height="700px"
options={options}
/>
diff --git a/src/views/tenant/standards/BestPracticeAnalyser.js b/src/views/tenant/standards/BestPracticeAnalyser.jsx
similarity index 90%
rename from src/views/tenant/standards/BestPracticeAnalyser.js
rename to src/views/tenant/standards/BestPracticeAnalyser.jsx
index a18f42909746..3c94d28db3fd 100644
--- a/src/views/tenant/standards/BestPracticeAnalyser.js
+++ b/src/views/tenant/standards/BestPracticeAnalyser.jsx
@@ -40,6 +40,7 @@ import { CellTip, cellGenericFormatter } from 'src/components/tables/CellGeneric
import { useExecBestPracticeAnalyserMutation } from 'src/store/api/reports'
import { ModalService } from 'src/components/utilities'
import { cellTableFormatter } from 'src/components/tables/CellTable'
+import { cellMathFormatter } from 'src/components/tables/CellMathFormatter'
const RefreshAction = ({ singleTenant = false, refreshFunction = null }) => {
const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName)
@@ -95,6 +96,7 @@ RefreshAction.propTypes = {
const getsubcolumns = (data) => {
const flatObj = data && data.length > 0 ? data : [{ data: 'No Data Found' }]
const QueryColumns = []
+
if (flatObj[0]) {
Object.keys(flatObj[0]).map((key) => {
QueryColumns.push({
@@ -186,6 +188,9 @@ const BestPracticeAnalyser = () => {
case 'table':
cellSelector = cellTableFormatter(col.value)
break
+ case 'math':
+ cellSelector = cellMathFormatter({ col })
+ break
default:
cellSelector = cellGenericFormatter()
break
@@ -216,8 +221,16 @@ const BestPracticeAnalyser = () => {
refresh: refreshValue,
},
})
- }, [Report, execGraphRequest, tenant.defaultDomainName, query, refreshValue, reportTemplate])
-
+ }, [
+ Report,
+ execGraphRequest,
+ tenant.defaultDomainName,
+ query,
+ refreshValue,
+ reportTemplate,
+ tenant.customerId,
+ SearchNow,
+ ])
return (
<>
@@ -324,7 +337,7 @@ const BestPracticeAnalyser = () => {
/>
{graphrequest.data.Columns.map((info, idx) => (
-