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}} +
{ + return ( + + + + + + + + + + Add Template + + + + {/**/} + {/* */} + {/*
{JSON.stringify(values, null, 2)}
*/} + {/*
*/} + {/*
*/} +
+ ) + }} + /> + + + ) +} + +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)}> + + + + + + + { + return ( + + + + + + + + + + + + + + + + Search + + + + + ) + }} + /> + + + + + +
    + + ) } 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" /> +