diff --git a/public/version_latest.txt b/public/version_latest.txt
index 6618ab5451bf..5b84073899a2 100644
--- a/public/version_latest.txt
+++ b/public/version_latest.txt
@@ -1 +1 @@
-2.17.0
\ No newline at end of file
+2.18.0
\ No newline at end of file
diff --git a/src/_nav.js b/src/_nav.js
index dfd872d9136b..e6d6ae3816d2 100644
--- a/src/_nav.js
+++ b/src/_nav.js
@@ -17,6 +17,7 @@ import {
faBus,
faExclamationTriangle,
faUserShield,
+ faEnvelope,
} from '@fortawesome/free-solid-svg-icons'
const _nav = [
@@ -133,35 +134,6 @@ const _nav = [
},
],
},
- {
- component: CNavGroup,
- name: 'Reports',
- section: 'Reports',
- to: '/tenant/reports',
- icon: ,
- items: [
- {
- component: CNavItem,
- name: 'Graph Explorer',
- to: '/tenant/administration/graph-explorer',
- },
- {
- component: CNavItem,
- name: 'Licence Report',
- to: '/tenant/administration/list-licenses',
- },
- {
- component: CNavItem,
- name: 'Consented Applications',
- to: '/tenant/administration/application-consent',
- },
- {
- component: CNavItem,
- name: 'Service Health',
- to: '/tenant/administration/service-health',
- },
- ],
- },
{
component: CNavGroup,
name: 'Standards',
@@ -235,6 +207,35 @@ const _nav = [
},
],
},
+ {
+ component: CNavGroup,
+ name: 'Reports',
+ section: 'Reports',
+ to: '/tenant/reports',
+ icon: ,
+ items: [
+ {
+ component: CNavItem,
+ name: 'Graph Explorer',
+ to: '/tenant/administration/graph-explorer',
+ },
+ {
+ component: CNavItem,
+ name: 'Licence Report',
+ to: '/tenant/administration/list-licenses',
+ },
+ {
+ component: CNavItem,
+ name: 'Consented Applications',
+ to: '/tenant/administration/application-consent',
+ },
+ {
+ component: CNavItem,
+ name: 'Service Health',
+ to: '/tenant/administration/service-health',
+ },
+ ],
+ },
{
component: CNavTitle,
name: 'Security & Compliance',
@@ -333,7 +334,7 @@ const _nav = [
},
{
component: CNavItem,
- name: 'Add WinGet or Store App',
+ name: 'Add Store App',
to: '/endpoint/applications/add-winget-app',
},
{
@@ -517,7 +518,7 @@ const _nav = [
},
{
component: CNavGroup,
- name: 'Transport Rules',
+ name: 'Transport',
section: 'Transport Rules',
to: '/tenant/administration',
icon: ,
@@ -554,6 +555,30 @@ const _nav = [
},
],
},
+ {
+ component: CNavGroup,
+ name: 'Spamfilter',
+ section: 'Spamfilter',
+ to: '/tenant/administration',
+ icon: ,
+ items: [
+ {
+ component: CNavItem,
+ name: 'Spamfilter',
+ to: '/email/spamfilter/list-spamfilter',
+ },
+ {
+ component: CNavItem,
+ name: 'Apply Spamfilter Template',
+ to: '/email/spamfilter/deploy',
+ },
+ {
+ component: CNavItem,
+ name: 'Templates',
+ to: '/email/spamfilter/list-templates',
+ },
+ ],
+ },
{
component: CNavGroup,
name: 'Reports',
diff --git a/src/components/buttons/PdfButton.js b/src/components/buttons/PdfButton.js
index f61c20172f30..0671910acec4 100644
--- a/src/components/buttons/PdfButton.js
+++ b/src/components/buttons/PdfButton.js
@@ -5,14 +5,14 @@ import 'jspdf-autotable'
import PropTypes from 'prop-types'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faFilePdf } from '@fortawesome/free-solid-svg-icons'
+import { useSelector } from 'react-redux'
function ExportPDFButton(props) {
+ const base64 = useSelector((state) => state.app.reportImage)
const exportPDF = (pdfData, pdfHeaders, pdfSize = 'A4', reportName = 'report') => {
const unit = 'pt'
const size = pdfSize // Use A1, A2, A3 or A4
const orientation = 'landscape' // portrait or landscape
-
- const marginLeft = 40
const doc = new jsPDF(orientation, unit, size)
doc.setFontSize(10)
@@ -24,16 +24,16 @@ function ExportPDFButton(props) {
}
})
- const title = reportName
let content = {
- startY: 50,
+ startY: 100,
columns: headerObj,
body: pdfData,
- theme: 'grid',
+ theme: 'striped',
headStyles: { fillColor: [247, 127, 0] },
}
-
- doc.text(title, marginLeft, 40)
+ if (base64) {
+ doc.addImage(base64, 'png', 20, 20, 120, 100)
+ }
doc.autoTable(content)
doc.save(reportName + '.pdf')
}
diff --git a/src/components/forms/RFFComponents.js b/src/components/forms/RFFComponents.js
index d1e0521250cd..8659c07aa85a 100644
--- a/src/components/forms/RFFComponents.js
+++ b/src/components/forms/RFFComponents.js
@@ -6,11 +6,14 @@ import {
CFormSelect,
CFormSwitch,
CFormTextarea,
+ CSpinner,
} from '@coreui/react'
import Select from 'react-select'
+import AsyncSelect from 'react-select/async'
import { Field } from 'react-final-form'
import React from 'react'
import PropTypes from 'prop-types'
+import { useRef } from 'react'
/*
wrapper classes for React Final Form with CoreUI
@@ -312,7 +315,7 @@ export const RFFSelectSearch = ({
className="react-select-container"
classNamePrefix="react-select"
{...input}
- isClearable={true}
+ isClearable={false}
name={name}
id={name}
disabled={disabled}
diff --git a/src/components/tables/CellGenericFormat.js b/src/components/tables/CellGenericFormat.js
new file mode 100644
index 000000000000..f3016a14e28e
--- /dev/null
+++ b/src/components/tables/CellGenericFormat.js
@@ -0,0 +1,82 @@
+import React from 'react'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import {
+ faTimesCircle,
+ faCheckCircle,
+ faExclamationCircle,
+} from '@fortawesome/free-solid-svg-icons'
+import { CellBadge } from 'src/components/tables'
+import { CBadge, CTooltip } from '@coreui/react'
+
+const IconWarning = () =>
+const IconError = () =>
+const IconSuccess = () =>
+
+function nocolour(iscolourless, content) {
+ if (iscolourless) {
+ return {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 (
+
+ {String(cell)}
+
+ )
+}
+
+export const cellGenericFormatter =
+ ({ warning = false, reverse = false, colourless = true, noDataIsFalse } = {}) =>
+ (row, index, column, id) => {
+ const cell = column.selector(row)
+ if (cell === null || cell === undefined || cell.length === 0) {
+ return No Data
+ }
+ if (typeof cell === 'boolean') {
+ return CellBoolean({ cell, warning, reverse, colourless, noDataIsFalse })
+ }
+ if (typeof cell === 'string') {
+ console.log(cell)
+ return CellTip(cell)
+ }
+ }
diff --git a/src/components/tables/CippTable.js b/src/components/tables/CippTable.js
index c628dddd16b9..f3a01cbd3964 100644
--- a/src/components/tables/CippTable.js
+++ b/src/components/tables/CippTable.js
@@ -90,6 +90,7 @@ export default function CippTable({
error,
reportName,
columns = [],
+ dynamicColumns = true,
filterlist,
tableProps: {
keyField = 'id',
@@ -186,48 +187,51 @@ export default function CippTable({
}
if (!disablePDFExport) {
- const addColumn = (columnname) => {
- var index = columns.length - 1
- let alreadyInArray = columns.find((o) => o.exportSelector === columnname)
- if (!alreadyInArray) {
- columns.splice(index, 0, {
- name: columnname,
- selector: (row) => row[columnname],
- sortable: true,
- exportSelector: columnname,
- })
- } else {
- let indexOfExisting = columns.findIndex((o) => o.exportSelector === columnname)
- columns = columns.splice(indexOfExisting, 1)
+ if (dynamicColumns === true) {
+ const addColumn = (columnname) => {
+ var index = columns.length - 1
+ let alreadyInArray = columns.find((o) => o.exportSelector === columnname)
+ if (!alreadyInArray) {
+ columns.splice(index, 0, {
+ name: columnname,
+ selector: (row) => row[columnname],
+ sortable: true,
+ exportSelector: columnname,
+ })
+ } else {
+ let indexOfExisting = columns.findIndex((o) => o.exportSelector === columnname)
+ columns = columns.splice(indexOfExisting, 1)
+ }
+ setUpdatedColumns(Date())
}
- setUpdatedColumns(Date())
+
+ defaultActions.push([
+
+
+
+
+
+ {dataKeys() &&
+ dataKeys().map((item, idx) => {
+ return (
+ addColumn(item)}>
+ {columns.find((o) => o.exportSelector === item) && (
+
+ )}{' '}
+ {item}
+
+ )
+ })}
+
+ ,
+ ])
}
- defaultActions.push([
-
-
-
-
-
- {dataKeys() &&
- dataKeys().map((item, idx) => {
- return (
- addColumn(item)}>
- {columns.find((o) => o.exportSelector === item) && (
-
- )}{' '}
- {item}
-
- )
- })}
-
- ,
- ])
actions.forEach((action) => {
defaultActions.push(action)
})
@@ -288,7 +292,7 @@ export default function CippTable({
{!isFetching && error && Error loading data}
{!error && (
- {columns.length === updatedColumns.length && (
+ {(columns.length === updatedColumns.length || !dynamicColumns) && (
{
const { data: profile, isFetching, isLoading } = useLoadClientPrincipalQuery()
@@ -57,6 +58,12 @@ const CippProfile = () => {
+
+
+
+
+
+
>
)
}
diff --git a/src/components/utilities/ReportImage.js b/src/components/utilities/ReportImage.js
new file mode 100644
index 000000000000..44002450c002
--- /dev/null
+++ b/src/components/utilities/ReportImage.js
@@ -0,0 +1,53 @@
+import React from 'react'
+import { CButton, CCard, CCardBody, CCardHeader, CImage } from '@coreui/react'
+import { useDispatch, useSelector } from 'react-redux'
+import { setReportImage } from 'src/store/features/app'
+import countryList from 'src/data/countryList'
+import Select from 'react-select'
+import { useRef } from 'react'
+
+const ReportImage = () => {
+ const dispatch = useDispatch()
+ const inputRef = useRef(null)
+ const ReportImage = useSelector((state) => state.app.reportImage)
+ const Switchusage = (e) => {
+ const reader = new FileReader()
+ reader.readAsDataURL(e.target.files[0])
+ reader.onloadend = () => {
+ console.log(reader.result)
+ dispatch(setReportImage({ reportImage: reader.result }))
+ console.log(ReportImage)
+ }
+ }
+
+ return (
+
+ Upload a default report image
+
+ Switchusage(e)}
+ />
+
+ Suggested image size: 120x100. This is a per user setting.
+ inputRef.current.click()}
+ className="me-2"
+ >
+ Upload File
+
+
+
+
+
+
+ )
+}
+
+export default ReportImage
diff --git a/src/routes.js b/src/routes.js
index b6f9ba2480b3..a260e53a98a8 100644
--- a/src/routes.js
+++ b/src/routes.js
@@ -186,7 +186,16 @@ const AddTransportTemplate = React.lazy(() =>
const TransportDeploy = React.lazy(() =>
import('src/views/email-exchange/transport/DeployTransport'),
)
-
+const SpamfilterList = React.lazy(() => import('src/views/email-exchange/spamfilter/Spamfilter'))
+const SpamFilterTemplate = React.lazy(() =>
+ import('src/views/email-exchange/spamfilter/ListSpamfilterTemplates'),
+)
+const AddSpamFilterTemplate = React.lazy(() =>
+ import('src/views/email-exchange/spamfilter/AddSpamfilterTemplate'),
+)
+const SpamFilterDeploy = React.lazy(() =>
+ import('src/views/email-exchange/spamfilter/DeploySpamfilter'),
+)
const ConnectorList = React.lazy(() => import('src/views/email-exchange/connectors/ConnectorList'))
const ConnectorListTemplates = React.lazy(() =>
import('src/views/email-exchange/connectors/ListConnectorTemplates'),
@@ -360,7 +369,7 @@ const routes = [
},
{
path: '/endpoint/applications/add-winget-app',
- name: 'Add Choco App',
+ name: 'Add Store App',
component: ApplicationsAddWingetApp,
},
{
@@ -491,6 +500,26 @@ const routes = [
name: 'Transport Rule add Temmplate',
component: AddTransportTemplate,
},
+ {
+ path: '/email/spamfilter/list-spamfilter',
+ name: 'List Spamfilter',
+ component: SpamfilterList,
+ },
+ {
+ path: '/email/spamfilter/deploy',
+ name: 'Deploy Spamfilter',
+ component: SpamFilterDeploy,
+ },
+ {
+ path: '/email/spamfilter/list-templates',
+ name: 'Spamfilter Templates',
+ component: SpamFilterTemplate,
+ },
+ {
+ path: '/email/spamfilter/add-template',
+ name: 'Spamfilter Template',
+ component: AddSpamFilterTemplate,
+ },
{
name: 'Edit Mailbox Permissions',
path: '/email/administration/edit-mailbox-permissions',
diff --git a/src/scss/_themes.scss b/src/scss/_themes.scss
index 0ebf122a89b7..40462dbefaed 100644
--- a/src/scss/_themes.scss
+++ b/src/scss/_themes.scss
@@ -310,8 +310,8 @@
--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: #fff;
- --cipp-table-highlight-on-hover-color: var(--cyberdrain-dark);
+ --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);
--cipp-offcanvas-header-bg: var(--cui-color-white);
@@ -492,13 +492,15 @@
--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: var(--cyberdrain-darker);
- --cipp-table-highlight-on-hover-color: var(--cyberdrain-light);
+ --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);
--cipp-offcanvas-header-bg: var(--cyberdrain-dark);
--cipp-offcanvas-header-color: var(--cyberdrain-light);
-
+ .react-select__input-container {
+ color: var(--cyberdrain-light) !important;
+ }
.react-loading-skeleton {
--base-color: var(--cyberdrain-dark-striped);
--highlight-color: var(--cyberdrain-lighter);
diff --git a/src/store/features/app.js b/src/store/features/app.js
index a9c3ba068ce8..e4e1124d818d 100644
--- a/src/store/features/app.js
+++ b/src/store/features/app.js
@@ -37,6 +37,9 @@ export const appSlice = createSlice({
setDefaultusageLocation: (state, action) => {
state.usageLocation = action.payload?.usageLocation
},
+ setReportImage: (state, action) => {
+ state.reportImage = action.payload?.reportImage
+ },
},
})
@@ -48,6 +51,7 @@ export const {
setCurrentTheme,
setSidebarVisible,
setDefaultusageLocation,
+ setReportImage,
} = appSlice.actions
export default persistReducer(
diff --git a/src/views/cipp/CIPPSettings.js b/src/views/cipp/CIPPSettings.js
index 487ca766030c..ab393a9999ee 100644
--- a/src/views/cipp/CIPPSettings.js
+++ b/src/views/cipp/CIPPSettings.js
@@ -50,7 +50,7 @@ import { useLazyEditDnsConfigQuery, useLazyGetDnsConfigQuery } from 'src/store/a
import { useDispatch, useSelector } from 'react-redux'
import { cellBooleanFormatter, CellTip, CellTipIcon, CippTable } from 'src/components/tables'
import { CippPage, CippPageList } from 'src/components/layout'
-import { RFFCFormSwitch, RFFCFormInput, RFFCFormSelect, RFFCFormCheck } from 'src/components/forms'
+import { RFFCFormSwitch, RFFCFormInput, RFFCFormSelect } from 'src/components/forms'
import { Form } from 'react-final-form'
import useConfirmModal from 'src/hooks/useConfirmModal'
import { setCurrentTenant } from 'src/store/features/app'
@@ -131,6 +131,9 @@ const GeneralSettings = () => {
const [selectedTenants, setSelectedTenants] = useState([])
const [showMaxSelected, setShowMaxSelected] = useState(false)
const [tokenOffcanvasVisible, setTokenOffcanvasVisible] = useState(false)
+ const [runBackup, RunBackupResult] = useLazyGenericGetRequestQuery()
+ const [restoreBackup, restoreBackupResult] = useLazyGenericPostRequestQuery()
+
const maxSelected = 2
const tenantSelectorRef = useRef(null)
@@ -243,7 +246,23 @@ const GeneralSettings = () => {
pagination: false,
subheader: false,
}
-
+ const downloadTxtFile = (data) => {
+ const txtdata = [JSON.stringify(RunBackupResult.data.backup)]
+ const file = new Blob(txtdata, { type: 'text/plain' })
+ const element = document.createElement('a')
+ element.href = URL.createObjectURL(file)
+ element.download = 'CIPP-Backup' + Date.now() + '.json'
+ document.body.appendChild(element)
+ element.click()
+ }
+ const inputRef = useRef(null)
+ const handleChange = (e) => {
+ const fileReader = new FileReader()
+ fileReader.readAsText(e.target.files[0], 'UTF-8')
+ fileReader.onload = (e) => {
+ restoreBackup({ path: '/api/ExecRestoreBackup', values: e.target.result })
+ }
+ }
return (
@@ -402,6 +421,65 @@ const GeneralSettings = () => {
+
+
+
+
+ Run Backup
+
+
+ Click the button below to start a backup of all settings
+ runBackup({ path: '/api/ExecRunBackup' })}
+ disabled={RunBackupResult.isFetching}
+ className="me-3 mt-3"
+ >
+ {RunBackupResult.isFetching && (
+
+ )}
+ Run backup
+
+ handleChange(e)}
+ />
+ inputRef.current.click()}
+ disabled={restoreBackupResult.isFetching}
+ className="me-3 mt-3"
+ >
+ {restoreBackupResult.isFetching && (
+
+ )}
+ Restore backup
+
+ {restoreBackupResult.isSuccess && (
+ <>
+ {restoreBackupResult.data.Results}
+ >
+ )}
+ {RunBackupResult.isSuccess && (
+ <>
+
+ downloadTxtFile(RunBackupResult.data.backup)}
+ className="m-1"
+ >
+ Download Backup
+
+
+ >
+ )}
+
+
+
+
)
}
@@ -438,7 +516,12 @@ const ExcludedTenantsSettings = () => {
refreshPermissions({ path: `/api/ExecCPVPermissions?TenantFilter=${domain.customerId}` }),
})
const handleConfirmExcludeTenant = (tenant) => {
- addExcludeTenant(tenant)
+ ModalService.confirm({
+ title: 'Exclude Tenant',
+ body: Are you sure you want to exclude this tenant?
,
+ onConfirm: () => addExcludeTenant(tenant),
+ })
+
.unwrap()
.then(() => {
dispatch(setCurrentTenant({}))
@@ -501,7 +584,7 @@ const ExcludedTenantsSettings = () => {
size="sm"
variant="ghost"
color="danger"
- onClick={() => handleExcludeTenant(row)}
+ onClick={() => handleConfirmExcludeTenant({ value: row.customerId })}
>
diff --git a/src/views/cipp/Logs.js b/src/views/cipp/Logs.js
index 68ab5d87dfbe..7c91c5d46215 100644
--- a/src/views/cipp/Logs.js
+++ b/src/views/cipp/Logs.js
@@ -99,10 +99,7 @@ const Logs = () => {
.map((key) => key + '=' + shippedValues[key])
.join('&')
- //alert(JSON.stringify(values, null, 2))
navigate(`?${queryString}`)
- // @todo hook this up
- // genericPostRequest({ url: 'api/AddIntuneTemplate', values })
}
return (
@@ -170,11 +167,6 @@ const Logs = () => {
- {/**/}
- {/* */}
- {/* {JSON.stringify(values, null, 2)}
*/}
- {/* */}
- {/**/}
)
}}
diff --git a/src/views/cipp/Setup.js b/src/views/cipp/Setup.js
index 37171a398e64..22c07cc4a17f 100644
--- a/src/views/cipp/Setup.js
+++ b/src/views/cipp/Setup.js
@@ -107,7 +107,7 @@ const Setup = () => {
@@ -206,13 +206,15 @@ const Setup = () => {
+ you may enter your secrets below, if you only want to update a single value, leave the
+ other fields blank.
@@ -222,7 +224,7 @@ const Setup = () => {
type="text"
name="ApplicationID"
label="Application ID"
- placeholder="Enter the application ID. e.g 1111-1111-1111-1111-11111"
+ placeholder="Enter the application ID. e.g 1111-1111-1111-1111-11111. Leave blank to retain a previous key if this exists."
/>
@@ -232,7 +234,7 @@ const Setup = () => {
type="password"
name="ApplicationSecret"
label="Application Secret"
- placeholder="Enter the application secret"
+ placeholder="Enter the application secret. Leave blank to retain a previous key if this exists."
/>
@@ -242,7 +244,7 @@ const Setup = () => {
type="password"
name="RefreshToken"
label="Refresh Token"
- placeholder="Enter the refresh token"
+ placeholder="Enter the refresh token. Leave blank to retain a previous key if this exists."
/>
@@ -252,7 +254,7 @@ const Setup = () => {
type="password"
name="ExchangeRefreshToken"
label="Exchange Refresh Token"
- placeholder="Enter the Exchange refresh tokens"
+ placeholder="Enter the Exchange refresh tokens. Leave blank to retain a previous key if this exists."
/>
@@ -268,7 +270,6 @@ const Setup = () => {
{!postResults.isSuccess && (
{(props) => {
- /* eslint-disable react/prop-types */
return (
<>
diff --git a/src/views/email-exchange/spamfilter/AddSpamfilterTemplate.js b/src/views/email-exchange/spamfilter/AddSpamfilterTemplate.js
new file mode 100644
index 000000000000..73b0a0670b3f
--- /dev/null
+++ b/src/views/email-exchange/spamfilter/AddSpamfilterTemplate.js
@@ -0,0 +1,61 @@
+import React from 'react'
+import { CButton, CCallout, CCol, CForm, CRow, CSpinner } from '@coreui/react'
+import { Form } from 'react-final-form'
+import { CippContentCard, CippPage } from 'src/components/layout'
+import { RFFCFormTextarea } from 'src/components/forms'
+import { useLazyGenericPostRequestQuery } from 'src/store/api/app'
+
+const SpamFilterAddTemplate = () => {
+ const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
+
+ const handleSubmit = async (values) => {
+ // alert(JSON.stringify(values, null, 2))
+ // @todo hook this up
+ genericPostRequest({ path: '/api/AddTransportTemplate', values })
+ }
+
+ return (
+
+
+ {postResults.isFetching && (
+
+ Loading
+
+ )}
+ {postResults.isSuccess && {postResults.data.Results}}
+
+
+ )
+}
+
+export default SpamFilterAddTemplate
diff --git a/src/views/email-exchange/spamfilter/DeploySpamfilter.js b/src/views/email-exchange/spamfilter/DeploySpamfilter.js
new file mode 100644
index 000000000000..8c3a470efb20
--- /dev/null
+++ b/src/views/email-exchange/spamfilter/DeploySpamfilter.js
@@ -0,0 +1,202 @@
+import React from 'react'
+import { CCol, CRow, CCallout, 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 { RFFCFormSelect, RFFCFormTextarea } from 'src/components/forms'
+import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app'
+import { OnChange } from 'react-final-form-listeners'
+
+const Error = ({ name }) => (
+
+ touched && error ? (
+
+
+ {error}
+
+ ) : null
+ }
+ />
+)
+
+Error.propTypes = {
+ name: PropTypes.string.isRequired,
+}
+
+const requiredArray = (value) => (value && value.length !== 0 ? undefined : 'Required')
+const SpamFilterAdd = () => {
+ const [intuneGetRequest, intuneTemplates] = useLazyGenericGetRequestQuery()
+ const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
+
+ const handleSubmit = async (values) => {
+ values.selectedTenants.map(
+ (tenant) => (values[`Select_${tenant.defaultDomainName}`] = tenant.defaultDomainName),
+ )
+ values.TemplateType = values.Type
+ genericPostRequest({ path: '/api/AddSpamfilter', values: values })
+ }
+ const WhenFieldChanges = ({ field, set }) => (
+
+ {(
+ // No subscription. We only use Field to get to the change function
+ { input: { onChange } },
+ ) => (
+
+ {({ form }) => (
+
+ {(value) => {
+ let template = intuneTemplates.data.filter(function (obj) {
+ return obj.GUID === value
+ })
+ // console.log(template[0][set])
+ onChange(JSON.stringify(template[0]))
+ }}
+
+ )}
+
+ )}
+
+ )
+
+ const formValues = {
+ TemplateType: 'Admin',
+ }
+
+ return (
+
+
+
+ Step 1
+ Choose tenants
+
+
+
+ {(props) => (
+ row['displayName'],
+ sortable: true,
+ exportselector: 'displayName',
+ },
+ {
+ name: 'Default Domain Name',
+ selector: (row) => row['defaultDomainName'],
+ sortable: true,
+ exportselector: 'mail',
+ },
+ ]}
+ fieldProps={props}
+ />
+ )}
+
+
+
+
+
+
+ Step 2
+
+ Enter the raw JSON for this policy, or select from a template. You can create templates
+ from existing policies.
+
+
+
+
+
+ {intuneTemplates.isUninitialized &&
+ intuneGetRequest({ path: 'api/ListSpamFilterTemplates' })}
+ {intuneTemplates.isSuccess && (
+ ({
+ value: template.GUID,
+ label: template.name,
+ }))}
+ placeholder="Select a template"
+ label="Please choose a template to apply, or enter the information manually."
+ />
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+ Step 3
+ Confirm and apply
+
+
+ {!postResults.isSuccess && (
+
+ {(props) => {
+ return (
+ <>
+
+
+
+ Selected Tenants
+
+ {props.values.selectedTenants.map((tenant, idx) => (
+
+ {tenant.displayName}- {tenant.defaultDomainName}
+
+ ))}
+
+ Rule Settings
+ {props.values.PowerShellCommand}
+
+
+ >
+ )
+ }}
+
+ )}
+ {postResults.isFetching && (
+
+ Loading
+
+ )}
+ {postResults.isSuccess && (
+
+ {postResults.data.Results.map((message, idx) => {
+ return {message}
+ })}
+
+ )}
+
+
+
+ )
+}
+
+export default SpamFilterAdd
diff --git a/src/views/email-exchange/spamfilter/ListSpamfilterTemplates.js b/src/views/email-exchange/spamfilter/ListSpamfilterTemplates.js
new file mode 100644
index 000000000000..13afb7fa99a4
--- /dev/null
+++ b/src/views/email-exchange/spamfilter/ListSpamfilterTemplates.js
@@ -0,0 +1,124 @@
+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'
+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'
+
+const SpamFilterListTemplates = () => {
+ const tenant = useSelector((state) => state.app.currentTenant)
+
+ const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery()
+ const Offcanvas = (row, rowIndex, formatExtraData) => {
+ const [ocVisible, setOCVisible] = useState(false)
+ const handleDeleteIntuneTemplate = (apiurl, message) => {
+ ModalService.confirm({
+ title: 'Confirm',
+ body: {message}
,
+ onConfirm: () => ExecuteGetRequest({ path: apiurl }),
+ confirmLabel: 'Continue',
+ cancelLabel: 'Cancel',
+ })
+ }
+ return (
+ <>
+ setOCVisible(true)}>
+
+
+
+ handleDeleteIntuneTemplate(
+ `/api/RemoveSpamfilterTemplate?ID=${row.GUID}`,
+ 'Do you want to delete the template?',
+ )
+ }
+ >
+
+
+
+ setOCVisible(false)}
+ >
+
+
+ >
+ )
+ }
+
+ const columns = [
+ {
+ name: 'Display Name',
+ selector: (row) => row['name'],
+ sortable: true,
+ cell: (row) => CellTip(row['name']),
+ exportSelector: 'name',
+ },
+ {
+ name: 'High Confidence Spam Action',
+ selector: (row) => row['HighConfidenceSpamAction'],
+ sortable: true,
+ exportSelector: 'HighConfidenceSpamAction',
+ },
+ {
+ name: 'Bulk Spam Action',
+ selector: (row) => row['BulkSpamAction'],
+ sortable: true,
+ exportSelector: 'BulkSpamAction',
+ },
+ {
+ name: 'Phish Spam Action',
+ selector: (row) => row['PhishSpamAction'],
+ sortable: true,
+ exportSelector: 'PhishSpamAction',
+ },
+ {
+ name: 'GUID',
+ selector: (row) => row['GUID'],
+ sortable: true,
+ cell: (row) => CellTip(row['GUID']),
+ exportSelector: 'GUID',
+ },
+ {
+ name: 'Actions',
+ cell: Offcanvas,
+ maxWidth: '80px',
+ },
+ ]
+
+ return (
+ <>
+ {getResults.isFetching && (
+
+ Loading
+
+ )}
+ {getResults.isSuccess && {getResults.data?.Results}}
+ {getResults.isError && (
+ Could not connect to API: {getResults.error.message}
+ )}
+
+ >
+ )
+}
+
+export default SpamFilterListTemplates
diff --git a/src/views/email-exchange/spamfilter/Spamfilter.js b/src/views/email-exchange/spamfilter/Spamfilter.js
new file mode 100644
index 000000000000..f250ea4ef58a
--- /dev/null
+++ b/src/views/email-exchange/spamfilter/Spamfilter.js
@@ -0,0 +1,186 @@
+import { CButton } from '@coreui/react'
+import { faBan, faBook, faCheck, faEllipsisV, faTrash } 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 { CippActionsOffcanvas } from 'src/components/utilities'
+import { cellBooleanFormatter, cellDateFormatter, CellTip } from 'src/components/tables'
+
+const Offcanvas = (row, rowIndex, formatExtraData) => {
+ const tenant = useSelector((state) => state.app.currentTenant)
+ const [ocVisible, setOCVisible] = useState(false)
+ //console.log(row)
+ return (
+ <>
+ setOCVisible(true)}>
+
+
+ ,
+ modalBody: row,
+ modalType: 'POST',
+ modalUrl: `/api/AddSpamfilterTemplate`,
+ modalMessage: 'Are you sure you want to create a template based on this rule?',
+ },
+ {
+ label: 'Enable Rule',
+ color: 'info',
+ icon: ,
+ modal: true,
+ modalUrl: `/api/EditSpamfilter?State=enable&TenantFilter=${tenant.defaultDomainName}&name=${row.Name}`,
+ modalMessage: 'Are you sure you want to enable this rule?',
+ },
+ {
+ label: 'Disable Rule',
+ color: 'info',
+ icon: ,
+ modal: true,
+ modalUrl: `/api/EditSpamfilter?State=disable&TenantFilter=${tenant.defaultDomainName}&name=${row.Name}`,
+ modalMessage: 'Are you sure you want to disable this rule?',
+ },
+ {
+ label: 'Delete Rule',
+ color: 'danger',
+ modal: true,
+ icon: ,
+ modalUrl: `/api/RemoveSpamFilter?TenantFilter=${tenant.defaultDomainName}&name=${row.Name}`,
+ modalMessage: 'Are you sure you want to delete this rule?',
+ },
+ ]}
+ placement="end"
+ visible={ocVisible}
+ id={row.id}
+ hideFunction={() => setOCVisible(false)}
+ />
+ >
+ )
+}
+
+const columns = [
+ {
+ name: 'Name',
+ selector: (row) => row['Name'],
+ sortable: true,
+ wrap: true,
+ cell: (row) => CellTip(row['Name']),
+ exportSelector: 'Name',
+ },
+ {
+ name: 'Default Rule',
+ selector: (row) => row['IsDefault'],
+ sortable: true,
+ cell: cellBooleanFormatter(),
+ exportSelector: 'IsDefault',
+ },
+ {
+ name: 'Rule State',
+ selector: (row) => row['ruleState'],
+ sortable: true,
+ exportSelector: 'ruleState',
+ },
+ {
+ name: 'Priority',
+ selector: (row) => row['rulePrio'],
+ sortable: true,
+ exportSelector: 'rulePrio',
+ },
+ {
+ name: 'High Confidence Spam Action',
+ selector: (row) => row['HighConfidenceSpamAction'],
+ sortable: true,
+ exportSelector: 'HighConfidenceSpamAction',
+ },
+ {
+ name: 'Bulk Spam Action',
+ selector: (row) => row['BulkSpamAction'],
+ sortable: true,
+ exportSelector: 'BulkSpamAction',
+ },
+ {
+ name: 'Phish Spam Action',
+ selector: (row) => row['PhishSpamAction'],
+ sortable: true,
+ exportSelector: 'PhishSpamAction',
+ },
+ {
+ name: 'Creation Date',
+ selector: (row) => row['WhenCreated'],
+ sortable: true,
+ exportSelector: 'WhenChanged',
+ cell: cellDateFormatter(),
+ },
+ {
+ name: 'Last Edit Date',
+ selector: (row) => row['WhenChanged'],
+ sortable: true,
+ exportSelector: 'WhenChanged',
+ cell: cellDateFormatter(),
+ },
+ {
+ name: 'Actions',
+ cell: Offcanvas,
+ maxWidth: '80px',
+ },
+]
+
+const SpamFilterList = () => {
+ const tenant = useSelector((state) => state.app.currentTenant)
+
+ return (
+
+ )
+}
+
+export default SpamFilterList
diff --git a/src/views/endpoint/applications/ApplicationsAddChocoApp.js b/src/views/endpoint/applications/ApplicationsAddChocoApp.js
index 48c46c93314e..458f400245a9 100644
--- a/src/views/endpoint/applications/ApplicationsAddChocoApp.js
+++ b/src/views/endpoint/applications/ApplicationsAddChocoApp.js
@@ -1,13 +1,32 @@
import React from 'react'
-import { CCol, CRow, CForm, CListGroup, CListGroupItem, CCallout, CSpinner } from '@coreui/react'
+import {
+ CCol,
+ CRow,
+ CForm,
+ CListGroup,
+ CListGroupItem,
+ CCallout,
+ CSpinner,
+ CInputGroup,
+ CFormInput,
+ CButton,
+} 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 { RFFCFormInput, RFFCFormRadio, RFFCFormSwitch } from 'src/components/forms'
+import {
+ RFFCFormCheck,
+ RFFCFormInput,
+ RFFCFormRadio,
+ RFFCFormSelect,
+ RFFCFormSwitch,
+} 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 }) => (
(value && value.length !== 0 ? undefined : 'Req
const ApplyStandard = () => {
const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
+ const [searchPostRequest, foundPackages] = useLazyGenericPostRequestQuery()
const handleSubmit = async (values) => {
values.selectedTenants.map(
@@ -39,13 +59,42 @@ const ApplyStandard = () => {
)
genericPostRequest({ path: '/api/AddChocoApp', values: values })
}
-
+ const handleSearch = async (values) => {
+ searchPostRequest({
+ path: '/api/ListPotentialApps',
+ values: { type: 'Choco', searchString: values },
+ })
+ }
const formValues = {
InstallAsSystem: true,
DisableRestart: true,
AssignTo: 'On',
}
+ const searchRef = useRef(null)
+ const WhenFieldChanges = ({ field, set }) => (
+
+ {(
+ // No subscription. We only use Field to get to the change function
+ { input: { onChange } },
+ ) => (
+
+ {({ form }) => (
+
+ {(value) => {
+ let template = foundPackages.data.filter(function (obj) {
+ console.log(value)
+ return obj.packagename === value
+ })
+ console.log(template[0])
+ onChange(template[0][set])
+ }}
+
+ )}
+
+ )}
+
+ )
return (
{
+
+
+
+
+ handleSearch(searchRef.current.value)}
+ >
+ Search
+
+
+
+
+
+
+ {foundPackages.isFetching && }
+ {foundPackages.isSuccess && (
+ ({
+ value: packagename.packagename,
+ label: `${packagename.applicationName} - ${packagename.packagename}`,
+ }))}
+ placeholder={!foundPackages.isFetching ? 'Select package' : 'Loading...'}
+ name="PackageSelector"
+ />
+ )}
+
+
+
+
+
@@ -116,9 +202,10 @@ const ApplyStandard = () => {
+ Install options:
-
+
(
(value && value.length !== 0 ? undefined : 'Req
const AddWinGet = () => {
const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
+ const [searchPostRequest, foundPackages] = useLazyGenericPostRequestQuery()
const handleSubmit = async (values) => {
values.selectedTenants.map(
@@ -39,12 +59,45 @@ const AddWinGet = () => {
)
genericPostRequest({ path: '/api/AddWinGetApp', values: values })
}
+ const handleSearch = async (values) => {
+ searchPostRequest({
+ path: '/api/ListPotentialApps',
+ values: { type: 'WinGet', searchString: values },
+ })
+ }
+ const searchRef = useRef(null)
+
+ const packageIdRef = useRef(null)
+ const packageNameRef = useRef(null)
const formValues = {
InstallAsSystem: true,
DisableRestart: true,
AssignTo: 'On',
}
+ const WhenFieldChanges = ({ field, set }) => (
+
+ {(
+ // No subscription. We only use Field to get to the change function
+ { input: { onChange } },
+ ) => (
+
+ {({ form }) => (
+
+ {(value) => {
+ let template = foundPackages.data.filter(function (obj) {
+ console.log(value)
+ return obj.packagename === value
+ })
+ console.log(template[0])
+ onChange(template[0][set])
+ }}
+
+ )}
+
+ )}
+
+ )
return (
{
-
+
+
+ handleSearch(searchRef.current.value)}
+ >
+ Search
+
+
+
+
+
+
+ {foundPackages.isFetching && }
+ {foundPackages.isSuccess && (
+ ({
+ value: packagename.packagename,
+ label: `${packagename.applicationName} - ${packagename.packagename}`,
+ }))}
+ placeholder={!foundPackages.isFetching ? 'Select package' : 'Loading...'}
+ name="PackageSelector"
+ />
+ )}
+
+
+
+
+
+
+
+
-
+
@@ -111,32 +213,35 @@ const AddWinGet = () => {
-
-
-
-
-
-
-
+
+
+ Install options:
+
+
+
+
+
+
+
@@ -164,16 +269,6 @@ const AddWinGet = () => {
Description: {props.values.description}
-
- Custom Repo:
- {props.values.customRepo ? props.values.customRepo : ' No'}
-
-
- Install as System: {props.values.InstallAsSystem ? 'Yes' : 'No'}
-
-
- Disable Restart: {props.values.DisableRestart ? 'Yes' : 'No'}
-
Assign to: {props.values.AssignTo}
diff --git a/src/views/identity/administration/AddGroupTemplate.js b/src/views/identity/administration/AddGroupTemplate.js
index 80d54b54c575..e580ce4515b7 100644
--- a/src/views/identity/administration/AddGroupTemplate.js
+++ b/src/views/identity/administration/AddGroupTemplate.js
@@ -58,7 +58,7 @@ const AddGroupTemplate = () => {
-
+
{
const a = rowA.createdDateTime.toLowerCase()
const b = rowB.createdDateTime.toLowerCase()
@@ -78,18 +96,99 @@ const columns = [
const SignInsReport = () => {
const tenant = useSelector((state) => state.app.currentTenant)
+ let navigate = useNavigate()
+ let query = useQuery()
+ const filter = query.get('filter')
+ const DateFilter = query.get('DateFilter')
+ const searchparams = query.toString()
+ const [visibleA, setVisibleA] = useState(true)
+
+ const handleSubmit = async (values) => {
+ Object.keys(values).filter(function (x) {
+ if (values[x] === null) {
+ delete values[x]
+ }
+ return null
+ })
+ const shippedValues = {
+ SearchNow: true,
+ ...values,
+ }
+ var queryString = Object.keys(shippedValues)
+ .map((key) => key + '=' + shippedValues[key])
+ .join('&')
+
+ navigate(`?${queryString}`)
+ }
return (
-
+ <>
+
+
+
+
+
+ Sign In log Settings
+ setVisibleA(!visibleA)}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
)
}
diff --git a/src/views/security/defender/ListVuln.js b/src/views/security/defender/ListVuln.js
index e54d39c3f398..a2094b9275a8 100644
--- a/src/views/security/defender/ListVuln.js
+++ b/src/views/security/defender/ListVuln.js
@@ -99,7 +99,7 @@ const ListVuln = () => {
{ filterName: 'Vendor is Microsoft', filter: '"softwareVendor":"Microsoft"' },
{ filterName: 'High Severity', filter: '"vulnerabilitySeverityLevel":"High"' },
],
- params: { TenantFilter: tenant?.customerId },
+ params: { TenantFilter: tenant?.defaultDomainName },
}}
/>
)
diff --git a/src/views/tenant/administration/GraphExplorer.js b/src/views/tenant/administration/GraphExplorer.js
index aae8365a1bc6..55d6c11c7af4 100644
--- a/src/views/tenant/administration/GraphExplorer.js
+++ b/src/views/tenant/administration/GraphExplorer.js
@@ -22,6 +22,7 @@ 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()
@@ -63,6 +64,7 @@ const GraphExplorer = () => {
selector: (row) => row[`${value.toString()}`],
sortable: true,
exportSelector: value,
+ cell: cellGenericFormatter(),
}),
)
QueryColumns.set = true
@@ -167,6 +169,11 @@ const GraphExplorer = () => {
value:
'directoryRoles/roleTemplateId=62e90394-69f5-4237-9190-012177145e10/members',
},
+ {
+ label: 'Multifactor Authentication Report for Admins',
+ value:
+ '/reports/authenticationMethods/userRegistrationDetails?$filter=IsAdmin eq true',
+ },
{
label: 'Secure Score with Current Score and Max Score',
value:
@@ -218,6 +225,7 @@ const GraphExplorer = () => {
{
label: 'Exchange Portal',
color: 'info',
external: true,
- link: `https://outlook.office365.com/ecp/?rfr=Admin_o365&exsvurl=1&delegatedOrg=${row.defaultDomainName}`,
+ link: `https://admin.exchange.microsoft.com/?landingpage=homepage&form=mac_sidebar&delegatedOrg=${row.defaultDomainName}#`,
},
{
icon: ,
@@ -223,7 +223,7 @@ const TenantsList = () => {
center: true,
cell: (row) => (
{
name="standards.DisableM365GroupUsers"
label="Disable M365 Group creation by users"
/>
+
{
name="standards.DisableSecurityGroupUsers"
label="Disable Security Group creation by users"
/>
+