diff --git a/public/version_latest.txt b/public/version_latest.txt index c910885a0023..79a00e9a07f5 100644 --- a/public/version_latest.txt +++ b/public/version_latest.txt @@ -1 +1 @@ -2.15.0 \ No newline at end of file +2.16.0 \ No newline at end of file diff --git a/src/App.js b/src/App.js index e5167de71211..30112f0e0628 100644 --- a/src/App.js +++ b/src/App.js @@ -22,72 +22,74 @@ const App = () => { return ( }> - - - CIPP - - - } /> - } /> - } /> - } /> - } /> - } /> - - - - } - > - {routes.map((route, idx) => { - return ( - route.component && ( - + CIPP + + + } /> + } /> + } /> + } /> + } /> + } /> + + + + } + > + {routes.map((route, idx) => { + return ( + route.component && ( + }> + + CIPP - {route.name} + + + + + + } + /> + ) + ) + })} + {adminRoutes.map((route, idx) => { + return ( + route.component && ( + }> CIPP - {route.name} - - - } - /> - ) - ) - })} - {adminRoutes.map((route, idx) => { - return ( - route.component && ( - - }> - - CIPP - {route.name} - + - - - } - /> - ) + + + + } + /> ) - })} - } /> - - } /> - - + ) + })} + } /> + + } /> + ) diff --git a/src/_nav.js b/src/_nav.js index 2e26d8dc08bf..c2fdcf3e033d 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -117,7 +117,7 @@ const _nav = [ items: [ { component: CNavItem, - name: 'List Tenants', + name: 'Tenants', to: '/tenant/administration/tenants', }, { @@ -128,7 +128,7 @@ const _nav = [ { component: CNavItem, - name: 'List Scheduled Alerts', + name: 'Scheduled Alerts', to: '/tenant/administration/alertsqueue', }, ], @@ -171,12 +171,12 @@ const _nav = [ items: [ { component: CNavItem, - name: 'List and Edit Standards', + name: 'Edit Individual Standards', to: '/tenant/standards/list-applied-standards', }, { component: CNavItem, - name: 'Apply Standard', + name: 'Apply Standards', to: '/tenant/standards/apply-standard', }, { @@ -205,12 +205,12 @@ const _nav = [ items: [ { component: CNavItem, - name: 'List Policies', + name: 'Policies', to: '/tenant/conditional/list-policies', }, { component: CNavItem, - name: 'List Named Locations', + name: 'Named Locations', to: '/tenant/conditional/list-named-locations', }, { @@ -230,7 +230,7 @@ const _nav = [ }, { component: CNavItem, - name: 'List Templates', + name: 'Templates', to: '/tenant/conditional/list-template', }, ], @@ -248,12 +248,12 @@ const _nav = [ items: [ { component: CNavItem, - name: 'List Incidents', + name: 'Incidents', to: '/security/incidents/list-incidents', }, { component: CNavItem, - name: 'List Alerts', + name: 'Alerts', to: '/security/incidents/list-alerts', }, ], @@ -284,6 +284,11 @@ const _nav = [ name: 'Defender Status', to: '/security/defender/list-defender', }, + { + component: CNavItem, + name: 'Vulnerabilities', + to: '/security/defender/list-defender-tvm', + }, ], }, { @@ -313,12 +318,12 @@ const _nav = [ items: [ { component: CNavItem, - name: 'List Applications', + name: 'Applications', to: '/endpoint/applications/list', }, { component: CNavItem, - name: 'List Application Queue', + name: 'Application Queue', to: '/endpoint/applications/queue', }, { @@ -362,17 +367,17 @@ const _nav = [ }, { component: CNavItem, - name: 'List Autopilot Devices', + name: 'Autopilot Devices', to: '/endpoint/autopilot/list-devices', }, { component: CNavItem, - name: 'List Profiles', + name: 'Profiles', to: '/endpoint/autopilot/list-profiles', }, { component: CNavItem, - name: 'List Status Pages', + name: 'Status Pages', to: '/endpoint/autopilot/list-status-pages', }, ], @@ -386,12 +391,12 @@ const _nav = [ items: [ { component: CNavItem, - name: 'List Devices', + name: 'Devices', to: '/endpoint/reports/devices', }, { component: CNavItem, - name: 'List MEM Policies', + name: 'MEM Policies', to: '/endpoint/MEM/list-policies', }, { @@ -406,7 +411,7 @@ const _nav = [ }, { component: CNavItem, - name: 'List Templates', + name: 'Templates', to: '/endpoint/MEM/list-templates', }, ], @@ -424,7 +429,7 @@ const _nav = [ items: [ { component: CNavItem, - name: 'List OneDrive', + name: 'OneDrive', to: '/teams-share/onedrive/list', }, ], @@ -438,7 +443,7 @@ const _nav = [ items: [ { component: CNavItem, - name: 'List Sharepoint', + name: 'Sharepoint', to: '/teams-share/sharepoint/list-sharepoint', }, ], @@ -457,7 +462,7 @@ const _nav = [ }, { component: CNavItem, - name: 'List Teams', + name: 'Teams', to: '/teams-share/teams/list-team', }, { @@ -514,9 +519,14 @@ const _nav = [ items: [ { component: CNavItem, - name: 'List Transport rules', + name: 'Transport rules', to: '/email/transport/list-rules', }, + { + component: CNavItem, + name: 'Transport Templates', + to: '/email/transport/list-templates', + }, { component: CNavItem, name: 'Deploy Transport rule', @@ -524,13 +534,18 @@ const _nav = [ }, { component: CNavItem, - name: 'Add Template', - to: '/email/transport/add-template', + name: 'Connectors', + to: '/email/connectors/list-connectors', }, { component: CNavItem, - name: 'List Templates', - to: '/email/transport/list-templates', + name: 'Connector Templates', + to: '/email/connectors/list-connector-templates', + }, + { + component: CNavItem, + name: 'Deploy Connector Templates', + to: '/email/connectors/deploy-connector', }, ], }, diff --git a/src/components/forms/RFFComponents.js b/src/components/forms/RFFComponents.js index 86ebafeb6575..d1e0521250cd 100644 --- a/src/components/forms/RFFComponents.js +++ b/src/components/forms/RFFComponents.js @@ -258,7 +258,7 @@ RFFCFormSelect.propTypes = { values: PropTypes.arrayOf(PropTypes.shape({ label: PropTypes.string, value: PropTypes.any })), } -export function Condition({ when, is, children, like }) { +export function Condition({ when, is, children, like, regex }) { return ( <> {is && ( @@ -271,6 +271,11 @@ export function Condition({ when, is, children, like }) { {({ input: { value } }) => (value.includes(like) ? children : null)} )} + {regex && ( + + {({ input: { value } }) => (value.match(regex) ? children : null)} + + )} ) } diff --git a/src/components/layout/CippContentCard.js b/src/components/layout/CippContentCard.js index ae2b68417fa0..357ab4cfc442 100644 --- a/src/components/layout/CippContentCard.js +++ b/src/components/layout/CippContentCard.js @@ -12,7 +12,7 @@ export default function CippContentCard({ className = null, }) { return ( - + {title} {icon ? : null} diff --git a/src/components/utilities/ErrorBoundary.js b/src/components/utilities/ErrorBoundary.js index 5393f794d850..a32fd5e589f0 100644 --- a/src/components/utilities/ErrorBoundary.js +++ b/src/components/utilities/ErrorBoundary.js @@ -1,5 +1,6 @@ -import React from 'react' +import React, { Suspense } from 'react' import PropTypes from 'prop-types' +import Page500 from 'src/views/pages/page500/Page500' export default class ErrorBoundary extends React.Component { constructor(props) { @@ -18,17 +19,8 @@ export default class ErrorBoundary extends React.Component { render() { if (this.state.errorInfo) { - // Error path - return ( -
-

Something went wrong.

-
- {this.state.error && this.state.error.toString()} -
- {this.state.errorInfo.componentStack} -
-
- ) + //React.lazy(() => import('src/views/pages/page500/Page500')) + return } // Normally, just render children return this.props.children diff --git a/src/components/utilities/Toasts.js b/src/components/utilities/Toasts.js index 9265185a21ee..8ceee3836291 100644 --- a/src/components/utilities/Toasts.js +++ b/src/components/utilities/Toasts.js @@ -55,7 +55,9 @@ const Toast = ({ message, title, onClose, error }) => { -
{JSON.stringify(error, null, 2)}
+
+            {error?.status} - {error?.message}
+          
@@ -64,7 +66,7 @@ const Toast = ({ message, title, onClose, error }) => { Toast.propTypes = { title: PropTypes.string, - message: PropTypes.string, + message: PropTypes.any, onClose: PropTypes.func.isRequired, error: PropTypes.any, } diff --git a/src/components/utilities/UniversalSearch.js b/src/components/utilities/UniversalSearch.js new file mode 100644 index 000000000000..a99bc5a91830 --- /dev/null +++ b/src/components/utilities/UniversalSearch.js @@ -0,0 +1,101 @@ +import React, { useState } from 'react' +import { useDispatch } from 'react-redux' +import { CFormInput, CSpinner } from '@coreui/react' +import { hideSwitcher } from 'src/store/features/switcher' +import { useNavigate } from 'react-router-dom' +import PropTypes from 'prop-types' +import { useLazyGenericGetRequestQuery } from 'src/store/api/app' + +export const UniversalSearch = React.forwardRef( + ({ onConfirm = () => {}, onChange = () => {}, maxResults = 7, value = '' }, ref) => { + const [searchValue, setSearchValue] = useState(value) + const [getSearchItems, searchItems] = useLazyGenericGetRequestQuery() + + const handleChange = (event) => { + setSearchValue(event.target?.value) + } + + const handleKeyDown = (event) => { + if (event.key === 'Enter') { + // on enter key, start the search + getSearchItems({ path: `/api/ExecUniversalSearch?SearchObj=${searchValue}` }) + } + } + + return ( +
+
+ +
+ {searchItems.isSuccess && } + {searchItems.isFetching && ( + <> +
+
+
+
+ +
+
+
+
+ + )} +
+ ) + }, +) +UniversalSearch.displayName = 'UniversalSearch' +UniversalSearch.propTypes = { + ref: PropTypes.oneOfType([ + // Either a function + PropTypes.func, + // Or the instance of a DOM native element (see the note about SSR) + PropTypes.shape({ current: PropTypes.instanceOf(Element) }), + ]), + onConfirm: PropTypes.func, + onChange: PropTypes.func, + maxResults: PropTypes.number, + value: PropTypes.any, +} + +const Results = ({ items = [] }) => { + return items.map((item, key) => { + return + }) +} + +const ResultsRow = ({ match }) => { + const navigate = useNavigate() + const dispatch = useDispatch() + + const handleClick = () => { + dispatch(hideSwitcher()) + navigate(`/identity/administration/users?customerId=${match.customerId}`) + } + + return ( +
+
+
+
+
{match.displayName}
+
{match.userPrincipalName}
+ Found in tenant {match.defaultDomainName} +
+
+
+
+ ) +} + +ResultsRow.propTypes = { + match: PropTypes.array, +} diff --git a/src/routes.js b/src/routes.js index 88c75b7a2eb0..c068673a143f 100644 --- a/src/routes.js +++ b/src/routes.js @@ -21,9 +21,11 @@ const GroupTemplates = React.lazy(() => import('src/views/identity/administratio const EditGroup = React.lazy(() => import('src/views/identity/administration/EditGroup')) const ViewGroup = React.lazy(() => import('src/views/identity/administration/ViewGroup')) const Roles = React.lazy(() => import('src/views/identity/administration/Roles')) -const Devices = React.lazy(() => import('src/views/endpoint/MEM/Devices')) +const Devices = React.lazy(() => import('src/views/endpoint/intune/Devices')) const Page404 = React.lazy(() => import('src/views/pages/page404/Page404')) const Page403 = React.lazy(() => import('src/views/pages/page403/Page403')) +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')) @@ -105,21 +107,25 @@ const AutopilotListProfiles = React.lazy(() => const AutopilotListStatusPages = React.lazy(() => import('src/views/endpoint/autopilot/AutopilotListStatusPages'), ) -const IntuneListPolicies = React.lazy(() => import('src/views/endpoint/MEM/MEMListPolicies')) -const MEMEditPolicy = React.lazy(() => import('src/views/endpoint/MEM/MEMEditPolicy')) +const IntuneListPolicies = React.lazy(() => import('src/views/endpoint/intune/MEMListPolicies')) +const MEMEditPolicy = React.lazy(() => import('src/views/endpoint/intune/MEMEditPolicy')) const EditAutopilotProfile = React.lazy(() => import('src/views/endpoint/autopilot/AutopilotEditProfile'), ) const EditAutopilotStatusPage = React.lazy(() => import('src/views/endpoint/autopilot/AutopilotEditStatusPage'), ) -const IntuneCAPolicies = React.lazy(() => import('src/views/endpoint/MEM/MEMCAPolicies')) -const IntuneAddPolicy = React.lazy(() => import('src/views/endpoint/MEM/MEMAddPolicy')) -const MEMAddPolicyTemplate = React.lazy(() => import('src/views/endpoint/MEM/MEMAddPolicyTemplate')) +const IntuneCAPolicies = React.lazy(() => import('src/views/endpoint/intune/MEMCAPolicies')) +const IntuneAddPolicy = React.lazy(() => import('src/views/endpoint/intune/MEMAddPolicy')) +const MEMAddPolicyTemplate = React.lazy(() => + import('src/views/endpoint/intune/MEMAddPolicyTemplate'), +) const IntuneListPolicyTemplate = React.lazy(() => - import('src/views/endpoint/MEM/MEMListPolicyTemplates'), + import('src/views/endpoint/intune/MEMListPolicyTemplates'), ) const ListDefender = React.lazy(() => import('src/views/security/defender/ListDefender')) +const ListVulnerabilities = React.lazy(() => import('src/views/security/defender/ListVuln')) + const DeployDefender = React.lazy(() => import('src/views/security/defender/DeployDefender')) const OneDriveList = React.lazy(() => import('src/views/teams-share/onedrive/OneDriveList')) @@ -176,6 +182,18 @@ const AddTransportTemplate = React.lazy(() => const TransportDeploy = React.lazy(() => import('src/views/email-exchange/transport/DeployTransport'), ) + +const ConnectorList = React.lazy(() => import('src/views/email-exchange/connectors/ConnectorList')) +const ConnectorListTemplates = React.lazy(() => + import('src/views/email-exchange/connectors/ListConnectorTemplates'), +) +const DeployConnector = React.lazy(() => + import('src/views/email-exchange/connectors/DeployConnector'), +) +const AddConnectorTemplate = React.lazy(() => + import('src/views/email-exchange/connectors/AddConnectorTemplate'), +) + const SecurityComplianceAlerts = React.lazy(() => import('src/views/security/incidents/ListAlerts')) const SecurityComplianceIncidents = React.lazy(() => import('src/views/security/incidents/ListIncidents'), @@ -189,6 +207,7 @@ const routes = [ { path: '/cipp/logs', name: 'Logs', component: Logs }, { path: '/cipp/404', name: 'Error', component: Page404 }, { path: '/cipp/403', name: 'Error', component: Page403 }, + { path: '/cipp/500', name: 'Error', component: Page500 }, { path: '/identity', name: 'Identity' }, { path: '/identity/administration/users/add', name: 'Add User', component: AddUser }, { path: '/identity/administration/users/edit', name: 'Edit User', component: EditUser }, @@ -396,6 +415,12 @@ const routes = [ { path: '/security/defender', name: 'Defender' }, { path: '/security/defender/deployment', name: 'Deploy Defender', component: DeployDefender }, { path: '/security/defender/list-defender', name: 'List Defender', component: ListDefender }, + { + path: '/security/defender/list-defender-tvm', + name: 'List Vulnerabilities', + component: ListVulnerabilities, + }, + { path: '/teams-share', name: 'Teams & Sharepoint' }, { path: '/teams-share/onedrive', name: 'OneDrive' }, { path: '/teams-share/onedrive/list', name: 'List OneDrive', component: OneDriveList }, @@ -417,6 +442,26 @@ const routes = [ { name: 'Email & Exchange', path: '/email' }, { name: 'Email Administration', path: '/email/administration' }, { name: 'List Contacts', path: '/email/administration/contacts', component: ContactsList }, + { + path: '/email/connectors/list-connectors', + name: 'List connectors', + component: ConnectorList, + }, + { + path: '/email/connectors/deploy-connector', + name: 'Deploy connectors', + component: DeployConnector, + }, + { + path: '/email/connectors/add-connector-templates', + name: 'Add connectors Templates', + component: AddConnectorTemplate, + }, + { + path: '/email/connectors/list-connector-templates', + name: 'List connectors Templates', + component: ConnectorListTemplates, + }, { path: '/email/transport/list-rules', name: 'List Transport rules', diff --git a/src/store/api/app.js b/src/store/api/app.js index 89036a221ff6..220033b65d2d 100644 --- a/src/store/api/app.js +++ b/src/store/api/app.js @@ -73,10 +73,11 @@ export const appApi = baseApi.injectEndpoints({ }), }), execClearCache: builder.query({ - query: () => ({ + query: ({ tenantsOnly }) => ({ path: '/api/ListTenants', params: { ClearCache: true, + TenantsOnly: tenantsOnly, }, }), }), diff --git a/src/views/cipp/CIPPSettings.js b/src/views/cipp/CIPPSettings.js index 03383b245f71..f4cdec978688 100644 --- a/src/views/cipp/CIPPSettings.js +++ b/src/views/cipp/CIPPSettings.js @@ -48,7 +48,7 @@ import { useLazyEditDnsConfigQuery, useLazyGetDnsConfigQuery } from 'src/store/a import { useDispatch, useSelector } from 'react-redux' import { CippTable } from 'src/components/tables' import { CippPage, CippPageList } from 'src/components/layout' -import { RFFCFormSwitch, RFFCFormInput, RFFCFormSelect } from 'src/components/forms' +import { RFFCFormSwitch, RFFCFormInput, RFFCFormSelect, RFFCFormCheck } from 'src/components/forms' import { Form } from 'react-final-form' import useConfirmModal from 'src/hooks/useConfirmModal' import { setCurrentTenant } from 'src/store/features/app' @@ -225,11 +225,18 @@ const GeneralSettings = () => { const handleClearCache = useConfirmModal({ body:
Are you sure you want to clear the cache?
, onConfirm: () => { - clearCache() + clearCache({ tenantsOnly: false }) localStorage.clear() }, }) + const handleClearCacheTenant = useConfirmModal({ + body:
Are you sure you want to clear the cache?
, + onConfirm: () => { + clearCache({ tenantsOnly: true }) + }, + }) + const tableProps = { pagination: false, subheader: false, @@ -304,18 +311,27 @@ const GeneralSettings = () => { Clear Cache
- Click the button below to clear the all caches the application uses. This includes the - Best Practice Analyser, Tenant Cache, Domain Analyser, and personal settings such as - theme and usage location
+ Click the button below to clear the application cache. You can clear only the tenant + cache, or all caches.

handleClearCache()} disabled={clearCacheResult.isFetching} - className="mt-3" + className="me-3" + > + {clearCacheResult.isFetching && ( + + )} + Clear All Caches + + handleClearCacheTenant()} + disabled={clearCacheResult.isFetching} + className="me-3" > {clearCacheResult.isFetching && ( )} - Clear Cache + Clear Tenant Cache {clearCacheResult.isSuccess && (
{clearCacheResult.data?.Results}
diff --git a/src/views/email-exchange/connectors/AddConnectorTemplate.js b/src/views/email-exchange/connectors/AddConnectorTemplate.js new file mode 100644 index 000000000000..339ff4fd87d7 --- /dev/null +++ b/src/views/email-exchange/connectors/AddConnectorTemplate.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 TransportAddTemplate = () => { + 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 TransportAddTemplate diff --git a/src/views/email-exchange/connectors/ConnectorList.js b/src/views/email-exchange/connectors/ConnectorList.js new file mode 100644 index 000000000000..488f2a88e24a --- /dev/null +++ b/src/views/email-exchange/connectors/ConnectorList.js @@ -0,0 +1,160 @@ +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 { CellBoolean, cellBooleanFormatter, CellTip } from 'src/components/tables' +import { TitleButton } from 'src/components/buttons' + +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/AddExConnectorTemplate`, + modalMessage: 'Are you sure you want to create a template based on this rule?', + }, + { + label: 'Enable Rule', + color: 'info', + icon: , + modal: true, + modalUrl: `/api/EditExConnector?State=Enable&TenantFilter=${tenant.defaultDomainName}&GUID=${row.Guid}&Type=${row.cippconnectortype}`, + modalMessage: 'Are you sure you want to enable this rule?', + }, + { + label: 'Disable Rule', + color: 'info', + icon: , + modal: true, + modalUrl: `/api/EditExConnector?State=Disable&TenantFilter=${tenant.defaultDomainName}&GUID=${row.Guid}&Type=${row.cippconnectortype}`, + modalMessage: 'Are you sure you want to disable this rule?', + }, + { + label: 'Delete Rule', + color: 'danger', + modal: true, + icon: , + modalUrl: `/api/RemoveExConnector?TenantFilter=${tenant.defaultDomainName}&GUID=${row.Guid}&Type=${row.cippconnectortype}`, + modalMessage: 'Are you sure you want to disable 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: 'State', + selector: (row) => row['Enabled'], + cell: cellBooleanFormatter(), + sortable: true, + exportSelector: 'Enabled', + }, + { + name: 'Comment', + selector: (row) => row['Comment'], + sortable: true, + exportSelector: 'Comment', + }, + { + name: 'Direction', + selector: (row) => row['cippconnectortype'], + sortable: true, + exportSelector: 'cippconnectortype', + }, + { + name: 'Inbound Connector Hostname', + selector: (row) => row['TlsSenderCertificateName'], + exportSelector: 'TlsSenderCertificateName', + }, + { + name: 'Sender IP Addresses', + selector: (row) => row.SenderIPAddresses?.join(','), + exportSelector: 'SenderIPAddresses', + }, + { + name: 'Only apply via transport rules', + selector: (row) => row.IsTransportRuleScoped, + exportSelector: 'IsTransportRuleScoped', + cell: cellBooleanFormatter(), + }, + { + name: 'Smarthost', + selector: (row) => row.SmartHosts?.join(','), + exportSelector: 'Smarthost', + }, + { + name: 'TLS Settings', + selector: (row) => row.TlsSettings, + exportSelector: 'TlsSettings', + }, + { + name: 'TLS Domain', + selector: (row) => row.TlsDomain, + exportSelector: 'Smarthost', + }, + { + name: 'Actions', + cell: Offcanvas, + maxWidth: '80px', + }, +] + +const ConnectorList = () => { + const tenant = useSelector((state) => state.app.currentTenant) + + return ( + + + + } + tenantSelector={true} + datatable={{ + reportName: `${tenant?.defaultDomainName}-connectors-list`, + path: '/api/ListExchangeConnectors', + params: { TenantFilter: tenant?.defaultDomainName }, + columns, + }} + /> + ) +} + +export default ConnectorList diff --git a/src/views/email-exchange/connectors/DeployConnector.js b/src/views/email-exchange/connectors/DeployConnector.js new file mode 100644 index 000000000000..9b3d03e6c4ad --- /dev/null +++ b/src/views/email-exchange/connectors/DeployConnector.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 DeployConnectorTemplate = () => { + 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/AddExConnector', 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/ListExConnectorTemplates' })} + {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 DeployConnectorTemplate diff --git a/src/views/email-exchange/connectors/ListConnectorTemplates.js b/src/views/email-exchange/connectors/ListConnectorTemplates.js new file mode 100644 index 000000000000..b7c5abaca2cd --- /dev/null +++ b/src/views/email-exchange/connectors/ListConnectorTemplates.js @@ -0,0 +1,112 @@ +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' + +const ConnectorListTemplates = () => { + 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/RemoveExConnectorTemplate?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: 'Type', + selector: (row) => row['cippconnectortype'], + sortable: true, + cell: (row) => CellTip(row['cippconnectortype']), + exportSelector: 'cippconnectortype', + }, + { + 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 ConnectorListTemplates diff --git a/src/views/email-exchange/transport/ListTransportTemplates.js b/src/views/email-exchange/transport/ListTransportTemplates.js index 0efb373d42c4..3be990ce2db7 100644 --- a/src/views/email-exchange/transport/ListTransportTemplates.js +++ b/src/views/email-exchange/transport/ListTransportTemplates.js @@ -1,21 +1,14 @@ import React, { useState } from 'react' import { useSelector } from 'react-redux' import { CippCodeBlock, CippOffcanvas } from 'src/components/utilities' -import { CippDatatable, CellTip } from 'src/components/tables' -import { - CCardBody, - CButton, - CCallout, - CSpinner, - CCard, - CCardHeader, - CCardTitle, -} from '@coreui/react' +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 { CippPage } from 'src/components/layout' +import { CippPageList } from 'src/components/layout' import { ModalService } from 'src/components/utilities' +import { TitleButton } from 'src/components/buttons' const TransportListTemplates = () => { const tenant = useSelector((state) => state.app.currentTenant) @@ -94,31 +87,27 @@ const TransportListTemplates = () => { ] return ( - - - - Results - - - {getResults.isFetching && ( - - Loading - - )} - {getResults.isSuccess && {getResults.data?.Results}} - {getResults.isError && ( - Could not connect to API: {getResults.error.message} - )} - - - - + <> + {getResults.isFetching && ( + + Loading + + )} + {getResults.isSuccess && {getResults.data?.Results}} + {getResults.isError && ( + Could not connect to API: {getResults.error.message} + )} + } + datatable={{ + reportName: `${tenant?.defaultDomainName}-Groups`, + path: '/api/ListTransportRulesTemplates', + params: { TenantFilter: tenant?.defaultDomainName }, + columns, + }} + /> + ) } diff --git a/src/views/endpoint/applications/ApplicationsAddRMM.js b/src/views/endpoint/applications/ApplicationsAddRMM.js index 0cb50c486595..fe2d3bcd65c8 100644 --- a/src/views/endpoint/applications/ApplicationsAddRMM.js +++ b/src/views/endpoint/applications/ApplicationsAddRMM.js @@ -103,7 +103,7 @@ const AddRMM = () => { { {(props) => { - /* eslint-disable react/prop-types */ return ( <> @@ -324,7 +323,6 @@ const AddRMM = () => { {!postResults.isSuccess && ( {(props) => { - /* eslint-disable react/prop-types */ return ( <> diff --git a/src/views/endpoint/MEM/Devices.js b/src/views/endpoint/intune/Devices.js similarity index 94% rename from src/views/endpoint/MEM/Devices.js rename to src/views/endpoint/intune/Devices.js index d80ae8449101..2214ae201652 100644 --- a/src/views/endpoint/MEM/Devices.js +++ b/src/views/endpoint/intune/Devices.js @@ -21,19 +21,6 @@ const Offcanvas = (row, rowIndex, formatExtraData) => { extendedInfo={[ { label: 'Device Name', value: `${row.deviceName ?? ' '}` }, { label: 'UPN', value: `${row.userPrincipalName ?? ' '}` }, - row?.deviceActionResults.map((devicelog, idx) => { - if ($devicelog.actionName === 'locateDevice') { - return { - label: `${devicelog.startDateTime} - ${$devicelog.actionName}`, - value: devicelog.deviceLocation, - } - } else { - return { - label: `${devicelog.startDateTime} - ${$devicelog.actionName}`, - value: devicelog.actionState, - } - } - }), ]} actions={[ { diff --git a/src/views/endpoint/MEM/MEMAddPolicy.js b/src/views/endpoint/intune/MEMAddPolicy.js similarity index 88% rename from src/views/endpoint/MEM/MEMAddPolicy.js rename to src/views/endpoint/intune/MEMAddPolicy.js index bdee33a81d92..327954c91f40 100644 --- a/src/views/endpoint/MEM/MEMAddPolicy.js +++ b/src/views/endpoint/intune/MEMAddPolicy.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useState } from 'react' import { CCol, CRow, CListGroup, CListGroupItem, CCallout, CSpinner } from '@coreui/react' import { Field, FormSpy } from 'react-final-form' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' @@ -47,6 +47,12 @@ const AddPolicy = () => { values.TemplateType = values.Type genericPostRequest({ path: '/api/AddPolicy', values: values }) } + const [matchMap, setMatchMap] = useState([]) + const handleMap = (values) => { + if (JSON.stringify(values) != JSON.stringify(matchMap)) { + setMatchMap(values) + } + } const WhenFieldChanges = ({ field, set }) => ( {( @@ -121,8 +127,8 @@ const AddPolicy = () => {

    Step 2

    Enter the raw JSON for this policy. See{' '} - this - for more information. + this for more + information.

    @@ -187,11 +193,31 @@ const AddPolicy = () => { />
    - - - #create list of tenants here, with variable name - - + + {(props) => { + return ( + <> + + + {props.values.RAWJson?.match('%.*%') && + handleMap([...props.values.RAWJson.matchAll('%\\w+%')])} + {matchMap.map((varname) => + props.values.selectedTenants.map((item, index) => ( + + + + )), + )} + + + + ) + }} + { diff --git a/src/views/endpoint/MEM/MEMCAPolicies.js b/src/views/endpoint/intune/MEMCAPolicies.js similarity index 100% rename from src/views/endpoint/MEM/MEMCAPolicies.js rename to src/views/endpoint/intune/MEMCAPolicies.js diff --git a/src/views/endpoint/MEM/MEMEditPolicy.js b/src/views/endpoint/intune/MEMEditPolicy.js similarity index 100% rename from src/views/endpoint/MEM/MEMEditPolicy.js rename to src/views/endpoint/intune/MEMEditPolicy.js diff --git a/src/views/endpoint/MEM/MEMListPolicies.js b/src/views/endpoint/intune/MEMListPolicies.js similarity index 100% rename from src/views/endpoint/MEM/MEMListPolicies.js rename to src/views/endpoint/intune/MEMListPolicies.js diff --git a/src/views/endpoint/MEM/MEMListPolicyTemplates.js b/src/views/endpoint/intune/MEMListPolicyTemplates.js similarity index 100% rename from src/views/endpoint/MEM/MEMListPolicyTemplates.js rename to src/views/endpoint/intune/MEMListPolicyTemplates.js diff --git a/src/views/home/Home.js b/src/views/home/Home.js index c8e9970b6bf3..f777e911ae80 100644 --- a/src/views/home/Home.js +++ b/src/views/home/Home.js @@ -1,9 +1,9 @@ import React from 'react' -import { faBook, faExclamation } from '@fortawesome/free-solid-svg-icons' -import { CButton, CCallout } from '@coreui/react' +import { faBook, faExclamation, faSearch } from '@fortawesome/free-solid-svg-icons' +import { CButton, CCallout, CCol, CRow } from '@coreui/react' import { useLoadDashQuery, useLoadVersionsQuery } from 'src/store/api/app' -import { StatusIcon } from 'src/components/utilities' -import { CippContentCard, CippMasonry, CippMasonryItem } from 'src/components/layout' +import { FastSwitcher, StatusIcon } from 'src/components/utilities' +import { CippContentCard } from 'src/components/layout' import { CippTable } from 'src/components/tables' import { Link } from 'react-router-dom' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' @@ -25,47 +25,61 @@ const Home = () => { }, ] return ( - - <> - {dashboard?.Alerts && ( - - - {dashboard.Alerts.map((mappedAlert, idx) => ( + <> + + + + + + + + + + + + + + + {!isLoadingDash && dashboard.Alerts ? ( + dashboard.Alerts.map((mappedAlert, idx) => ( {mappedAlert} - ))} - - - )} - - + )) + ) : ( + No active Alerts + )} + + + + +
    {!isLoadingDash ? dashboard?.NextStandardsRun : }
    -
    - + +
    {!isLoadingDash ? dashboard?.NextBPARun : }
    -
    - + +
    {!isLoadingDash ? dashboard?.queuedApps : }
    -
    - + +
    {!isLoadingDash ? dashboard?.queuedStandards : }
    -
    - + +
    {!isLoadingDash ? dashboard?.tenantCount : }


    Managed tenants
    -
    - + +
    Refresh token: {!isLoadingDash ? dashboard?.RefreshTokenDate : ''} @@ -75,17 +89,15 @@ const Home = () => { Exchange Token: {!isLoadingDash ? dashboard?.ExchangeTokenDate : ''}
    -
    - - <> - + +
    Latest: {isSuccessVersion ? versions.RemoteCIPPVersion : }
    Current: {isSuccessVersion ? versions.LocalCIPPVersion : }
    -
    - + + {
    Latest: {isSuccessVersion ? versions.RemoteCIPPAPIVersion : }
    Current: {isSuccessVersion ? versions.LocalCIPPAPIVersion : }
    -
    - - - - {!isLoadingDash && issuccessDash && ( - - )} - {isLoadingDash && } - - - Jump to log - - - - -
    + + + + {!isLoadingDash && issuccessDash && ( + + )} + {isLoadingDash && } + + + Jump to log + + + + + + ) } diff --git a/src/views/identity/administration/AddUser.js b/src/views/identity/administration/AddUser.js index f5facb9b796a..ced12ede0f62 100644 --- a/src/views/identity/administration/AddUser.js +++ b/src/views/identity/administration/AddUser.js @@ -33,18 +33,20 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' import { useSelector } from 'react-redux' import { required } from 'src/validators' - -const passwordRequired = (value, values) => { - if (!values.Autopassword && !values.password) { - return 'Password or automatically set password required' - } - return undefined -} +import useQuery from 'src/hooks/useQuery' +import Select from 'react-select' +import { useNavigate } from 'react-router-dom' const AddUser = () => { + let navigate = useNavigate() + const tenant = useSelector((state) => state.app.currentTenant) const { defaultDomainName: tenantDomain } = tenant - + let query = useQuery() + const allQueryObj = {} + for (const [key, value] of query.entries()) { + allQueryObj[key] = value + } const { data: users = [], isFetching: usersIsFetching, @@ -91,7 +93,7 @@ const AddUser = () => { Usagelocation: values.usageLocation ? values.usageLocation.value : '', Username: values.mailNickname, streetAddress: values.streetAddress, - Autopassword: values.Autopassword, + Autopassword: !!values.Autopassword, MustChangePass: values.MustChangePass, tenantID: tenantDomain, ...values.license, @@ -101,10 +103,19 @@ const AddUser = () => { } const usagelocation = useSelector((state) => state.app.usageLocation) const initialState = { - Autopassword: true, + Autopassword: false, usageLocation: usagelocation, + ...allQueryObj, + } + const copyUserVariables = (t) => { + for (const [key, value] of Object.entries(t.value)) { + query.delete(key) + if (value != null) { + query.append(key, value) + } + navigate(`?${query.toString()}`) + } } - return ( {postResults.isSuccess && ( @@ -117,21 +128,20 @@ const AddUser = () => { - {adcIsFetching && } - {adcError && Unable to determine Azure AD Connect Settings} - {!adcIsFetching && - adconnectsettings.dirSyncEnabled && - adconnectsettings.dirSyncConfigured && ( - - Warning! {adconnectsettings.dirSyncEnabled} This tenant currently has Active - Directory Sync Enabled and Configured. This usually means users should be created - in Active Directory - - )} Account Details + {adcError && Unable to determine Azure AD Connect Settings} + {!adcIsFetching && + adconnectsettings.dirSyncEnabled && + adconnectsettings.dirSyncConfigured && ( + + Warning! {adconnectsettings.dirSyncEnabled} This tenant currently has Active + Directory Sync Enabled and Configured. This usually means users should be + created in Active Directory + + )} { Settings - - + + - + { - {/* Temporarily disabled, API does not support this yet. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ({ value: user.mail, - name: user.displayName, + name: `${user.displayName} <${user.userPrincipalName}>`, }))} placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} name="CopyFrom" @@ -339,6 +332,31 @@ const AddUser = () => { + + + + Copy properties + + + Use this option to copy the properties from another user, this will only copy the + visible fields as a template. +