diff --git a/src/_nav.jsx b/src/_nav.jsx index fbf1ff50f013..da05474f3a34 100644 --- a/src/_nav.jsx +++ b/src/_nav.jsx @@ -142,6 +142,11 @@ const _nav = [ name: 'Enterprise Applications', to: '/tenant/administration/enterprise-apps', }, + { + component: CNavItem, + name: 'Secure Score', + to: '/tenant/administration/securescore', + }, { component: CNavItem, name: 'App Consent Requests', @@ -157,6 +162,11 @@ const _nav = [ name: 'Tenant Offboarding', to: '/tenant/administration/tenant-offboarding-wizard', }, + { + component: CNavItem, + name: 'Partner Relationships', + to: '/tenant/administration/partner-relationships', + }, ], }, { diff --git a/src/components/contentcards/CippButtonCard.jsx b/src/components/contentcards/CippButtonCard.jsx new file mode 100644 index 000000000000..1540c09a888f --- /dev/null +++ b/src/components/contentcards/CippButtonCard.jsx @@ -0,0 +1,27 @@ +import React from 'react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { CCard, CCardBody, CCardFooter, CCardHeader, CCardTitle } from '@coreui/react' +import Skeleton from 'react-loading-skeleton' + +export default function CippButtonCard({ + title, + titleType = 'normal', + CardButton, + children, + isFetching, +}) { + return ( + + + + {titleType === 'big' ?

{title}

: title} +
+
+ + {isFetching && } + {children} + + {CardButton} +
+ ) +} diff --git a/src/components/forms/RFFComponents.jsx b/src/components/forms/RFFComponents.jsx index 234e990ca5c5..c1880eee88b8 100644 --- a/src/components/forms/RFFComponents.jsx +++ b/src/components/forms/RFFComponents.jsx @@ -444,6 +444,12 @@ export const RFFSelectSearch = ({ return ( {({ meta, input }) => { + const handleChange = onChange + ? (e) => { + input.onChange(e) + onChange(e) + } + : input.onChange return (
@@ -473,7 +479,7 @@ export const RFFSelectSearch = ({ options={selectSearchvalues} placeholder={placeholder} isMulti={multi} - onChange={onChange} + onChange={handleChange} onInputChange={debounceOnInputChange} inputValue={inputText} isLoading={isLoading} @@ -510,7 +516,7 @@ export const RFFSelectSearch = ({ options={selectSearchvalues} placeholder={placeholder} isMulti={multi} - onChange={onChange} + onChange={handleChange} onInputChange={debounceOnInputChange} inputValue={inputText} isLoading={isLoading} diff --git a/src/components/layout/AppHeader.jsx b/src/components/layout/AppHeader.jsx index 43ec9c476d35..9661c89e7eba 100644 --- a/src/components/layout/AppHeader.jsx +++ b/src/components/layout/AppHeader.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, useRef } from 'react' import { useSelector, useDispatch } from 'react-redux' import { CAlert, @@ -72,8 +72,29 @@ const AppHeader = () => { loadCippQueue() } + function useInterval(callback, delay, state) { + const savedCallback = useRef() + + // Remember the latest callback. + useEffect(() => { + savedCallback.current = callback + }) + + // Set up the interval. + useEffect(() => { + function tick() { + savedCallback.current() + } + + if (delay !== null) { + let id = setInterval(tick, delay) + return () => clearInterval(id) + } + }, [delay, state]) + } + useEffect(() => { - if (cippQueueList.isFetching || cippQueueList.isLoading) { + if (cippQueueList.isUninitialized && (cippQueueList.isFetching || cippQueueList.isLoading)) { setCippQueueExtendedInfo([ { label: 'Fetching recent jobs', @@ -82,26 +103,40 @@ const AppHeader = () => { link: '#', }, ]) - } - if ( - cippQueueList.isSuccess && - Array.isArray(cippQueueList.data) && - cippQueueList.data.length > 0 - ) { - setCippQueueExtendedInfo( - cippQueueList.data?.map((job) => ({ - label: `${job.Name}`, - value: job.Status, - link: job.Link, - timestamp: job.Timestamp, - })), - ) } else { - setCippQueueExtendedInfo([ - { label: 'No jobs to display', value: '', timpestamp: Date(), link: '#' }, - ]) + if ( + cippQueueList.isSuccess && + Array.isArray(cippQueueList.data) && + cippQueueList.data.length > 0 + ) { + setCippQueueExtendedInfo( + cippQueueList.data?.map((job) => ({ + label: `${job.Name}`, + value: job.Status, + link: job.Link, + timestamp: job.Timestamp, + percent: job.PercentComplete, + progressText: `${job.PercentComplete}%`, + })), + ) + } else { + setCippQueueExtendedInfo([ + { label: 'No jobs to display', value: '', timestamp: Date(), link: '#' }, + ]) + } } - }, [cippQueueList]) + }, [cippQueueList, setCippQueueExtendedInfo]) + + useInterval( + async () => { + if (cippQueueVisible) { + setCippQueueRefresh((Math.random() + 1).toString(36).substring(7)) + getCippQueueList({ path: 'api/ListCippQueue', params: { refresh: cippQueueRefresh } }) + } + }, + 5000, + cippQueueVisible, + ) const SwitchTheme = () => { let targetTheme = preferredTheme @@ -197,6 +232,7 @@ const AppHeader = () => { extendedInfo={[]} cards={cippQueueExtendedInfo} refreshFunction={refreshCippQueue} + isRefreshing={cippQueueList.isFetching || cippQueueList.isLoading} actions={[ { label: 'Clear History', diff --git a/src/components/tables/CippTable.jsx b/src/components/tables/CippTable.jsx index dbcde884e90d..3ba432f63677 100644 --- a/src/components/tables/CippTable.jsx +++ b/src/components/tables/CippTable.jsx @@ -16,6 +16,7 @@ import { CAccordionHeader, CAccordionBody, CAccordionItem, + CTooltip, } from '@coreui/react' import DataTable, { createTheme } from 'react-data-table-component' import PropTypes from 'prop-types' @@ -31,13 +32,12 @@ import { faSync, } from '@fortawesome/free-solid-svg-icons' import { cellGenericFormatter } from './CellGenericFormat' -import { ModalService } from '../utilities' +import { CippCodeOffCanvas, ModalService } from '../utilities' import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' import { debounce } from 'lodash-es' import { useSearchParams } from 'react-router-dom' import CopyToClipboard from 'react-copy-to-clipboard' import { setDefaultColumns } from 'src/store/features/app' -import M365Licenses from 'src/data/M365Licenses' const FilterComponent = ({ filterText, onFilter, onClear, filterlist, onFilterPreset }) => ( <> @@ -155,6 +155,7 @@ export default function CippTable({ const [filterviaURL, setFilterviaURL] = React.useState(false) const [originalColumns, setOrginalColumns] = React.useState(columns) const [updatedColumns, setUpdatedColumns] = React.useState(columns) + const [codeOffcanvasVisible, setCodeOffcanvasVisible] = useState(false) if (defaultColumns && defaultColumnsSet === false && endpointName) { const defaultColumnsArray = defaultColumns.split(',').filter((item) => item) @@ -579,7 +580,6 @@ export default function CippTable({ } const executeselectedAction = (item) => { - // console.log(item) setModalContent({ item, }) @@ -605,16 +605,18 @@ export default function CippTable({ } if (refreshFunction) { defaultActions.push([ - { - refreshFunction((Math.random() + 1).toString(36).substring(7)) - }} - className="m-1" - size="sm" - > - - , + + { + refreshFunction((Math.random() + 1).toString(36).substring(7)) + }} + className="m-1" + size="sm" + > + + + , ]) } @@ -815,6 +817,20 @@ export default function CippTable({ , ]) } + defaultActions.push([ + + { + setCodeOffcanvasVisible(true) + }} + className="m-1" + size="sm" + > + + + , + ]) return ( <>
@@ -982,6 +998,13 @@ export default function CippTable({ {...rest} /> {selectedRows.length >= 1 && Selected {selectedRows.length} items} + setCodeOffcanvasVisible(false)} + title="API Response" + /> )}
diff --git a/src/components/tables/WizardTableField.jsx b/src/components/tables/WizardTableField.jsx index ac65a722029d..85f213d14d51 100644 --- a/src/components/tables/WizardTableField.jsx +++ b/src/components/tables/WizardTableField.jsx @@ -24,7 +24,6 @@ export default class WizardTableField extends React.Component { } handleSelect = ({ selectedRows = [] }) => { - // console.log(selectedRows) const { fieldProps, keyField } = this.props if (selectedRows.length > 0) { fieldProps.input.onChange(selectedRows) diff --git a/src/components/utilities/CippActionsOffcanvas.jsx b/src/components/utilities/CippActionsOffcanvas.jsx index c2cab6d24539..f67e3d11ed88 100644 --- a/src/components/utilities/CippActionsOffcanvas.jsx +++ b/src/components/utilities/CippActionsOffcanvas.jsx @@ -5,14 +5,20 @@ import { CCallout, CCard, CCardBody, + CCardFooter, CCardHeader, CCardText, CCardTitle, + CCol, CFormInput, CFormSelect, CListGroup, CListGroupItem, COffcanvasTitle, + CProgress, + CProgressBar, + CProgressStacked, + CRow, CSpinner, } from '@coreui/react' import { CippCodeBlock, CippOffcanvas, ModalService } from 'src/components/utilities' @@ -222,8 +228,23 @@ export default function CippActionsOffcanvas(props) { {action.value && Status: {action.value}} - {action.timestamp && } + + + {action?.percent > 0 && ( + +
+ + {action?.progressText} + +
+
+ )} + + {action.timestamp && } + +
+
)) @@ -295,6 +316,7 @@ export default function CippActionsOffcanvas(props) { id={props.id} hideFunction={props.hideFunction} refreshFunction={props.refreshFunction} + isRefreshing={props.isRefreshing} > {getResults.isFetching && ( @@ -310,6 +332,7 @@ export default function CippActionsOffcanvas(props) { )} diff --git a/src/components/utilities/CippCodeBlock.jsx b/src/components/utilities/CippCodeBlock.jsx index aad81e02d78d..00ccd3c54f7c 100644 --- a/src/components/utilities/CippCodeBlock.jsx +++ b/src/components/utilities/CippCodeBlock.jsx @@ -16,6 +16,7 @@ function CippCodeBlock({ callout = false, calloutColour = 'info', calloutCopyValue = false, + dismissable = false, }) { const [codeCopied, setCodeCopied] = useState(false) @@ -36,7 +37,11 @@ function CippCodeBlock({ {codeCopied ? : } - {callout && {code}} + {callout && ( + + {code} + + )} {!callout && ( { - //console.log('refresh') props.refreshFunction() }} > - + {props.isRefreshing ? : } )} @@ -48,6 +47,7 @@ export const CippOffcanvasPropTypes = { id: PropTypes.string, hideFunction: PropTypes.func.isRequired, refreshFunction: PropTypes.func, + isRefreshing: PropTypes.bool, addedClass: PropTypes.string, } diff --git a/src/data/Extensions.json b/src/data/Extensions.json index 4d31c1df3dbc..0f26e93f26a5 100644 --- a/src/data/Extensions.json +++ b/src/data/Extensions.json @@ -112,7 +112,7 @@ "type": "NinjaOne", "cat": "Documentation & Monitoring", "forceSyncButton": true, - "helpText": "NOTE: This integration requires version 5.6 of NinjaOne, which rolls out regionally between the end of November and mid-December. This integration allows you to populate custom fields with Tenant information, monitor device compliance state, document other items and generate relationships inside NinjaOne.", + "helpText": "This integration allows you to populate custom fields with Tenant information, monitor device compliance state, document other items and generate relationships inside NinjaOne.", "SettingOptions": [ { "type": "input", diff --git a/src/data/standards.json b/src/data/standards.json index cab53f3144d6..ecde5a79d459 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -38,7 +38,7 @@ { "name": "standards.AuditLog", "cat": "Global Standards", - "tag": ["lowimpact", "CIS"], + "tag": ["lowimpact", "CIS", "mip_search_auditlog"], "helpText": "Enables the Unified Audit Log for tracking and auditing activities. Also runs Enable-OrganizationCustomization if necessary.", "addedComponent": [], "label": "Enable the Unified Audit Log", @@ -63,7 +63,7 @@ { "name": "standards.EnableCustomerLockbox", "cat": "Global Standards", - "tag": ["lowimpact", "CIS"], + "tag": ["lowimpact", "CIS", "CustomerLockBoxEnabled"], "helpText": "Enables Customer Lockbox that offers an approval process for Microsoft support to access organization data", "addedComponent": [], "label": "Enable Customer Lockbox", @@ -103,7 +103,7 @@ { "name": "standards.ActivityBasedTimeout", "cat": "Global Standards", - "tag": ["mediumimpact", "CIS"], + "tag": ["mediumimpact", "CIS", "spo_idle_session_timeout"], "helpText": "Enables and sets Idle session timeout for Microsoft 365 to 1 hour. This policy affects most M365 web apps", "addedComponent": [], "label": "Enable 1 hour Activity based Timeout", @@ -225,7 +225,7 @@ { "name": "standards.PasswordExpireDisabled", "cat": "Entra (AAD) Standards", - "tag": ["lowimpact", "CIS"], + "tag": ["lowimpact", "CIS", "PWAgePolicyNew"], "helpText": "Disables the expiration of passwords for the tenant by setting the password expiration policy to never expire for any user.", "addedComponent": [], "label": "Do not expire passwords", @@ -535,7 +535,7 @@ { "name": "standards.EnableMailTips", "cat": "Exchange Standards", - "tag": ["lowimpact", "CIS"], + "tag": ["lowimpact", "CIS", "exo_mailtipsenabled"], "helpText": "Enables all MailTips in Outlook. MailTips are the notifications Outlook and Outlook on the web shows when an email you create, meets some requirements", "addedComponent": [ { @@ -582,7 +582,7 @@ { "name": "standards.EnableMailboxAuditing", "cat": "Exchange Standards", - "tag": ["lowimpact", "CIS"], + "tag": ["lowimpact", "CIS", "exo_mailboxaudit"], "helpText": "Enables Mailbox auditing for all mailboxes and on tenant level. Disables audit bypass on all mailboxes. Unified Audit Log needs to be enabled for this standard to function.", "addedComponent": [], "label": "Enable Mailbox auditing", @@ -664,7 +664,7 @@ { "name": "standards.DisableExternalCalendarSharing", "cat": "Exchange Standards", - "tag": ["lowimpact", "CIS"], + "tag": ["lowimpact", "CIS", "exo_individualsharing"], "helpText": "Disables the ability for users to share their calendar with external users. Only for the default policy, so exclusions can be made if needed.", "addedComponent": [], "label": "Disable external calendar sharing", @@ -674,7 +674,7 @@ { "name": "standards.DisableAdditionalStorageProviders", "cat": "Exchange Standards", - "tag": ["lowimpact", "CIS"], + "tag": ["lowimpact", "CIS", "exo_storageproviderrestricted"], "helpText": "Disables the ability for users to open files in Outlook on the Web, from other providers such as Box, Dropbox, Facebook, Google Drive, OneDrive Personal, etc.", "addedComponent": [], "label": "Disable additional storage providers in OWA", @@ -684,7 +684,7 @@ { "name": "standards.DisableOutlookAddins", "cat": "Exchange Standards", - "tag": ["mediumimpact", "CIS"], + "tag": ["mediumimpact", "CIS", "exo_outlookaddins"], "helpText": "Disables the ability for users to install add-ins in Outlook. This is to prevent users from installing malicious add-ins.", "addedComponent": [], "label": "Disable users from installing add-ins in Outlook", @@ -765,7 +765,7 @@ { "name": "standards.SafeLinksPolicy", "cat": "Defender Standards", - "tag": ["lowimpact", "CIS"], + "tag": ["lowimpact", "CIS", "mdo_safelinksforemail", "mdo_safelinksforOfficeApps"], "helpText": "This creates a safelink policy that automatically scans, tracks, and and enables safe links for Email, Office, and Teams for both external and internal senders", "addedComponent": [ { @@ -791,7 +791,16 @@ { "name": "standards.AntiPhishPolicy", "cat": "Defender Standards", - "tag": ["lowimpact", "CIS"], + "tag": [ + "lowimpact", + "CIS", + "mdo_safeattachments", + "mdo_highconfidencespamaction", + "mdo_highconfidencephishaction", + "mdo_phisspamacation", + "mdo_spam_notifications_only_for_admins", + "mdo_antiphishingpolicies" + ], "helpText": "This creates a Anti-Phishing policy that automatically enables Mailbox Intelligence and spoofing, optional switches for Mailtips.", "addedComponent": [ { @@ -870,7 +879,13 @@ { "name": "standards.SafeAttachmentPolicy", "cat": "Defender Standards", - "tag": ["lowimpact", "CIS"], + "tag": [ + "lowimpact", + "CIS", + "mdo_safedocuments", + "mdo_commonattachmentsfilter", + "mdo_safeattachmentpolicy" + ], "helpText": "This creates a Safe Attachment policy", "addedComponent": [ { @@ -946,7 +961,7 @@ { "name": "standards.MalwareFilterPolicy", "cat": "Defender Standards", - "tag": ["lowimpact", "CIS"], + "tag": ["lowimpact", "CIS", "mdo_zapspam", "mdo_zapphish", "mdo_zapmalware"], "helpText": "This creates a Malware filter policy that enables the default File filter and Zero-hour auto purge for malware.", "addedComponent": [ { diff --git a/src/importsMap.jsx b/src/importsMap.jsx index ab783b321471..1ce828c16643 100644 --- a/src/importsMap.jsx +++ b/src/importsMap.jsx @@ -33,6 +33,7 @@ import React from 'react' "/identity/reports/azure-ad-connect-report": React.lazy(() => import('./views/identity/reports/AzureADConnectReport')), "/tenant/administration/tenants": React.lazy(() => import('./views/tenant/administration/Tenants')), "/tenant/administration/tenants/edit": React.lazy(() => import('./views/tenant/administration/EditTenant')), + "/tenant/administration/partner-relationships": React.lazy(() => import('./views/tenant/administration/PartnerRelationships')), "/tenant/administration/domains": React.lazy(() => import('./views/tenant/administration/Domains')), "/tenant/administration/alertswizard": React.lazy(() => import('./views/tenant/administration/AlertWizard')), "/tenant/administration/alertrules": React.lazy(() => import('./views/tenant/administration/AlertRules')), @@ -125,6 +126,7 @@ import React from 'react' "/license": React.lazy(() => import('./views/pages/license/License')), "/cipp/settings": React.lazy(() => import('./views/cipp/app-settings/CIPPSettings')), "/cipp/setup": React.lazy(() => import('./views/cipp/Setup')), + "/tenant/administration/securescore": React.lazy(() => import('./views/tenant/administration/SecureScore')), "/tenant/administration/gdap": React.lazy(() => import('./views/tenant/administration/GDAPWizard')), "/tenant/administration/gdap-invite": React.lazy(() => import('./views/tenant/administration/GDAPInviteWizard')), "/tenant/administration/gdap-role-wizard": React.lazy(() => import('./views/tenant/administration/GDAPRoleWizard')), diff --git a/src/routes.json b/src/routes.json index db9ee316185f..7355cb3b9905 100644 --- a/src/routes.json +++ b/src/routes.json @@ -222,6 +222,12 @@ "component": "views/tenant/administration/EditTenant", "allowedRoles": ["admin", "editor", "readonly"] }, + { + "path": "/tenant/administration/partner-relationships", + "name": "Partner Relationships", + "component": "views/tenant/administration/PartnerRelationships", + "allowedRoles": ["admin", "editor", "readonly"] + }, { "path": "/tenant/administration/domains", "name": "Domains", @@ -864,6 +870,12 @@ "component": "views/cipp/Setup", "allowedRoles": ["admin"] }, + { + "path": "/tenant/administration/securescore", + "name": "Secure Score Management", + "component": "views/tenant/administration/SecureScore", + "allowedRoles": ["admin", "editor", "readonly"] + }, { "path": "/tenant/administration/gdap", "name": "GDAP Wizard", diff --git a/src/scss/_themes.scss b/src/scss/_themes.scss index 3c1be2d0cecf..f05c2be45f9b 100644 --- a/src/scss/_themes.scss +++ b/src/scss/_themes.scss @@ -479,7 +479,7 @@ --cui-toast-color: var(--cui-color-black); --cui-toast-header-color: var(--cui-color-black); --cui-card-cap-color: var(--cyberdrain-white); - --cui-card-cap-bg: var(--cui-color-dark); + --cui-card-cap-bg: var(--cui-color-gray-hover); --cui-tertiary-bg: var(--cui-bgcolor-table-header); // CIPP Impact theme variables. --cipp-toast-bg: var(--cui-color-header-bar); diff --git a/src/store/api/baseQuery.js b/src/store/api/baseQuery.js index 896401eccb83..90173f3b7cc2 100644 --- a/src/store/api/baseQuery.js +++ b/src/store/api/baseQuery.js @@ -19,8 +19,6 @@ export const axiosQuery = async ({ path, method = 'get', params, data, hideToast }) return { data: result.data } // Successful response } catch (error) { - console.log('error', error) - console.log('path', path) if (attempt === retryDelays.length || !shouldRetry(error, path)) { return { // Max retries reached or error should not trigger a retry diff --git a/src/views/cipp/UserSettings.jsx b/src/views/cipp/UserSettings.jsx index 8a4081b8c29b..cedc16b9e672 100644 --- a/src/views/cipp/UserSettings.jsx +++ b/src/views/cipp/UserSettings.jsx @@ -218,13 +218,10 @@ const UserSettings = () => { .reduce((acc, val) => acc.concat(val.items), []) //only map if 'name' property is not null .filter((item) => item?.name) - .map((item) => - // console.log(item), - ({ - name: item?.name, - value: { to: item?.to, name: item?.name }, - }), - )} + .map((item) => ({ + name: item?.name, + value: { to: item?.to, name: item?.name }, + }))} allowCreate={false} refreshFunction={() => setRandom3((Math.random() + 1).toString(36).substring(7)) diff --git a/src/views/cipp/app-settings/SettingsBackend.jsx b/src/views/cipp/app-settings/SettingsBackend.jsx index 58e3bc2b594b..3dc9a9548af5 100644 --- a/src/views/cipp/app-settings/SettingsBackend.jsx +++ b/src/views/cipp/app-settings/SettingsBackend.jsx @@ -11,149 +11,92 @@ import { CRow, } from '@coreui/react' import { CippCodeBlock, CippOffcanvas } from 'src/components/utilities/index.js' +import CippButtonCard from 'src/components/contentcards/CippButtonCard' /** * The SettingsBackend method is responsible for rendering a settings panel that contains several resource * groups and corresponding links to access them. * The panel displays information about Resource Group, Key Vault, Static Web App (Role Management), * Function App (Deployment Center), Function App (Configuration), Function App (Overview), and Cloud Shell. - * + * Wow Kevin, you went hard, sorry I'm going to run it again. // Kelvin 22-04-2024. * @returns {JSX.Element} The settings panel component. */ + +const BackendCardList = [ + { + title: 'Resource Group', + description: + 'The Resource group contains all the CIPP resources in your tenant, except the SAM Application', + link: 'ResourceGroup', + }, + { + title: 'Key Vault', + description: + 'The keyvault allows you to check token information. By default you do not have access.', + link: 'KeyVault', + }, + { + title: 'Static Web App (Role Management)', + description: + 'The Static Web App role management allows you to invite other users to the application.', + link: 'SWARoles', + }, + { + title: 'Function App (Deployment Center)', + description: 'The Function App Deployment Center allows you to run updates on the API', + link: 'FunctionDeployment', + }, + { + title: 'Function App (Configuration)', + description: + 'At the Function App Configuration you can check the status of the API access to your keyvault', + link: 'FunctionConfig', + }, + { + title: 'Function App (Overview)', + description: 'At the function App Overview, you can stop and start the backend API', + link: 'FunctionApp', + }, +] + export function SettingsBackend() { const [listBackend, listBackendResult] = useLazyGenericGetRequestQuery() const [visible, setVisible] = useState(false) + const generateButton = (title, link) => ( + window.open(`${listBackendResult.data?.Results?.[link]}`, '_blank')} + rel="noreferrer" + > + {title} + + ) + return ( -
+ <> {listBackendResult.isUninitialized && listBackend({ path: 'api/ExecBackendURLs' })} - <> - - - - - Resource Group - - -

- The Resource group contains all the CIPP resources in your tenant, except the SAM - Application -

- - Go to Resource Group - -
-
-
- - - - Key Vault - - -

- The keyvault allows you to check token information. By default you do not have - access. -

- - Go to Keyvault - -
-
-
- - - - Static Web App (Role Management) - - -

- The Static Web App role management allows you to invite other users to the - application. -

- - Go to Role Management - -
-
-
-
- - - - - Function App (Deployment Center) - - -

The Function App Deployment Center allows you to run updates on the API

- - Go to Function App Deployment Center - -
-
+ + {BackendCardList.map((card, index) => ( + + + {card.description} + - - - - Function App (Configuration) - - -

- At the Function App Configuration you can check the status of the API access to - your keyvault -

- - Go to Function App Configuration - -
-
-
- - - - Function App (Overview) - - -

At the function App Overview, you can stop and start the backend API

- - Go to Function App Overview - -
-
-
-
- - - - - Cloud Shell - - -

Launch an Azure Cloud Shell Window

+ ))} + + + {' '} window.open( 'https://shell.azure.com/powershell', @@ -163,89 +106,91 @@ export function SettingsBackend() { } rel="noreferrer" > - Cloud Shell + Cloud Shell - setVisible(true)} className="mb-3"> + setVisible(true)} className="me-2"> Command Reference -
-
-
-
- setVisible(false)} - title="Command Reference" - > -
Function App Config
- -
Function App Deployment
- -
Watch Function Logs
- -
Static Web App Config
- -
List CIPP Users
- } - showLineNumbers={false} - wrapLongLines={true} - /> -
- -
+ > +

Launch an Azure Cloud Shell Window

+ + + + setVisible(false)} + title="Command Reference" + > +
Function App Config
+ +
Function App Deployment
+ +
Watch Function Logs
+ +
Static Web App Config
+ +
List CIPP Users
+ +
+ ) } diff --git a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx index 5386afbf1fbd..4676f197a217 100644 --- a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx +++ b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx @@ -1,22 +1,14 @@ import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app.js' -import { - CButton, - CCallout, - CCard, - CCardBody, - CCardHeader, - CCardText, - CCardTitle, - CCol, - CForm, - CSpinner, -} from '@coreui/react' +import { CButton, CCallout, CCardText, CCol, CForm, CRow, CSpinner, CTooltip } from '@coreui/react' import { Form } from 'react-final-form' import { RFFSelectSearch } from 'src/components/forms/index.js' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' -import React from 'react' +import React, { useEffect } from 'react' import { CippCallout } from 'src/components/layout/index.js' +import CippButtonCard from 'src/components/contentcards/CippButtonCard' +import { CippTable } from 'src/components/tables' +import { CellTip } from 'src/components/tables/CellGenericFormat' /** * Retrieves and sets the extension mappings for HaloPSA and NinjaOne. @@ -24,6 +16,12 @@ import { CippCallout } from 'src/components/layout/index.js' * @returns {JSX.Element} - JSX component representing the settings extension mappings. */ export function SettingsExtensionMappings() { + const [addedAttributes, setAddedAttribute] = React.useState(1) + const [mappingArray, setMappingArray] = React.useState('defaultMapping') + const [mappingValue, setMappingValue] = React.useState({}) + const [haloMappingsArray, setHaloMappingsArray] = React.useState([]) + const [ninjaMappingsArray, setNinjaMappingsArray] = React.useState([]) + const [HaloAutoMap, setHaloAutoMap] = React.useState(false) const [listHaloBackend, listBackendHaloResult = []] = useLazyGenericGetRequestQuery() const [listNinjaOrgsBackend, listBackendNinjaOrgsResult] = useLazyGenericGetRequestQuery() const [listNinjaFieldsBackend, listBackendNinjaFieldsResult] = useLazyGenericGetRequestQuery() @@ -35,16 +33,25 @@ export function SettingsExtensionMappings() { const [setNinjaFieldsExtensionconfig, extensionNinjaFieldsConfigResult] = useLazyGenericPostRequestQuery() - const onHaloSubmit = (values) => { + const onHaloSubmit = () => { + const originalFormat = haloMappingsArray.reduce((acc, item) => { + acc[item.Tenant?.customerId] = { label: item.haloName, value: item.haloId } + return acc + }, {}) setHaloExtensionconfig({ path: 'api/ExecExtensionMapping?AddMapping=Halo', - values: { mappings: values }, + values: { mappings: originalFormat }, }) } - const onNinjaOrgsSubmit = (values) => { + const onNinjaOrgsSubmit = () => { + const originalFormat = ninjaMappingsArray.reduce((acc, item) => { + acc[item.Tenant?.customerId] = { label: item.ninjaName, value: item.ninjaId } + return acc + }, {}) + setNinjaOrgsExtensionconfig({ path: 'api/ExecExtensionMapping?AddMapping=NinjaOrgs', - values: { mappings: values }, + values: { mappings: originalFormat }, }) } @@ -65,8 +72,158 @@ export function SettingsExtensionMappings() { values: { mappings: values }, }) } + + const onHaloAutomap = () => { + const newMappings = listBackendHaloResult.data?.Tenants.map( + (tenant) => { + const haloClient = listBackendHaloResult.data?.HaloClients.find( + (client) => client.name === tenant.displayName, + ) + if (haloClient) { + console.log(haloClient) + console.log(tenant) + return { + Tenant: tenant, + haloName: haloClient.name, + haloId: haloClient.value, + } + } + }, + //filter out any undefined values + ).filter((item) => item !== undefined) + setHaloMappingsArray((currentHaloMappings) => [...currentHaloMappings, ...newMappings]) + + setHaloAutoMap(true) + } + + useEffect(() => { + if (listBackendHaloResult.isSuccess) { + setHaloMappingsArray( + Object.keys(listBackendHaloResult.data?.Mappings).map((key) => ({ + Tenant: listBackendHaloResult.data?.Tenants.find((tenant) => tenant.customerId === key), + haloName: listBackendHaloResult.data?.Mappings[key].label, + haloId: listBackendHaloResult.data?.Mappings[key].value, + })), + ) + } + }, [listBackendHaloResult.isSuccess]) + + useEffect(() => { + if (listBackendNinjaOrgsResult.isSuccess) { + setNinjaMappingsArray( + Object.keys(listBackendNinjaOrgsResult.data?.Mappings).map((key) => ({ + Tenant: listBackendNinjaOrgsResult.data?.Tenants.find( + (tenant) => tenant.customerId === key, + ), + ninjaName: listBackendNinjaOrgsResult.data?.Mappings[key].label, + ninjaId: listBackendNinjaOrgsResult.data?.Mappings[key].value, + })), + ) + } + }, [ + listBackendNinjaOrgsResult.data?.Mappings, + listBackendNinjaOrgsResult.data?.Tenants, + listBackendNinjaOrgsResult.isSuccess, + ]) + + const Offcanvas = (row, rowIndex, formatExtraData) => { + return ( + <> + + + row.haloId + ? setHaloMappingsArray((currentHaloMappings) => + currentHaloMappings.filter((item) => item !== row), + ) + : setNinjaMappingsArray((currentNinjaMappings) => + currentNinjaMappings.filter((item) => item !== row), + ) + } + > + + + + + ) + } + const halocolumns = [ + { + name: 'Tenant', + selector: (row) => row.Tenant?.displayName, + sortable: true, + cell: (row) => CellTip(row.Tenant?.displayName), + exportSelector: 'Tenant', + }, + { + name: 'TenantId', + selector: (row) => row.Tenant?.customerId, + sortable: true, + exportSelector: 'Tenant/customerId', + omit: true, + }, + { + name: 'Halo Client Name', + selector: (row) => row['haloName'], + sortable: true, + cell: (row) => CellTip(row['haloName']), + exportSelector: 'haloName', + }, + { + name: 'Halo ID', + selector: (row) => row['haloId'], + sortable: true, + cell: (row) => CellTip(row['haloId']), + exportSelector: 'haloId', + }, + { + name: 'Actions', + cell: Offcanvas, + maxWidth: '80px', + }, + ] + + const ninjacolumns = [ + { + name: 'Tenant', + selector: (row) => row.Tenant?.displayName, + sortable: true, + cell: (row) => CellTip(row.Tenant?.displayName), + exportSelector: 'Tenant', + }, + { + name: 'TenantId', + selector: (row) => row.Tenant?.customerId, + sortable: true, + exportSelector: 'Tenant/customerId', + omit: true, + }, + { + name: 'NinjaOne Organization Name', + selector: (row) => row['ninjaName'], + sortable: true, + cell: (row) => CellTip(row['ninjaName']), + exportSelector: 'ninjaName', + }, + { + name: 'NinjaOne Organization ID', + selector: (row) => row['ninjaId'], + sortable: true, + cell: (row) => CellTip(row['ninjaId']), + exportSelector: 'ninjaId', + }, + { + name: 'Actions', + cell: Offcanvas, + maxWidth: '80px', + }, + ] + return ( -
+ {listBackendHaloResult.isUninitialized && listHaloBackend({ path: 'api/ExecExtensionMapping?List=Halo' })} {listBackendNinjaOrgsResult.isUninitialized && @@ -74,11 +231,28 @@ export function SettingsExtensionMappings() { {listBackendNinjaFieldsResult.isUninitialized && listNinjaFieldsBackend({ path: 'api/ExecExtensionMapping?List=NinjaFields' })} <> - - - HaloPSA Mapping Table - - + + + + {extensionHaloConfigResult.isFetching && ( + + )} + Save Mappings + + onHaloAutomap()} className="me-2"> + {extensionNinjaOrgsAutomapResult.isFetching && ( + + )} + Automap HaloPSA Clients + + + } + > {listBackendHaloResult.isFetching ? ( ) : ( @@ -87,27 +261,77 @@ export function SettingsExtensionMappings() { initialValues={listBackendHaloResult.data?.Mappings} render={({ handleSubmit, submitting, values }) => { return ( - + - Use the table below to map your client to the correct PSA client - {listBackendHaloResult.isSuccess && - listBackendHaloResult.data.Tenants?.map((tenant) => ( + Use the table below to map your client to the correct PSA client. + { + //load all the existing mappings and show them first in a table. + listBackendHaloResult.isSuccess && ( + + ) + } + + ({ + name: tenant.displayName, + value: tenant.customerId, + }))} + onChange={(e) => { + setMappingArray(e.value) + }} /> - ))} + + + + + + ({ + name: client.name, + value: client.value, + }))} + onChange={(e) => setMappingValue(e)} + placeholder="Select a HaloPSA Client" + /> + + + //set the new mapping in the array + setHaloMappingsArray([ + ...haloMappingsArray, + { + Tenant: listBackendHaloResult.data?.Tenants.find( + (tenant) => tenant.customerId === mappingArray, + ), + haloName: mappingValue.label, + haloId: mappingValue.value, + }, + ]) + } + className={`my-4 circular-button`} + title={'+'} + > + + + - - {extensionHaloConfigResult.isFetching && ( - - )} - Set Mappings - + {HaloAutoMap && ( + + Automapping has been executed. Remember to check the changes and save + them. + + )} {(extensionHaloConfigResult.isSuccess || extensionHaloConfigResult.isError) && !extensionHaloConfigResult.isFetching && ( @@ -122,18 +346,171 @@ export function SettingsExtensionMappings() { )} + + + After editing the mappings you must click Save Mappings for the changes to + take effect. The table will be saved exactly as presented. + + + ) + }} + /> + )} + + + + {' '} + + + {extensionNinjaOrgsConfigResult.isFetching && ( + + )} + Set Mappings + + onNinjaOrgsAutomap()} className="me-2"> + {extensionNinjaOrgsAutomapResult.isFetching && ( + + )} + Automap NinjaOne Organizations + + + } + > + {listBackendNinjaOrgsResult.isFetching ? ( + + ) : ( +
{ + return ( + + + Use the table below to map your client to the correct NinjaOne Organization. + { + //load all the existing mappings and show them first in a table. + listBackendNinjaOrgsResult.isSuccess && ( + + ) + } + + + ({ + name: tenant.displayName, + value: tenant.customerId, + }))} + onChange={(e) => { + setMappingArray(e.value) + }} + /> + + + + + + ({ + name: client.name, + value: client.value, + }))} + onChange={(e) => setMappingValue(e)} + placeholder="Select a NinjaOne Organization" + /> + + + //set the new mapping in the array + setNinjaMappingsArray([ + ...ninjaMappingsArray, + { + Tenant: listBackendNinjaOrgsResult.data?.Tenants.find( + (tenant) => tenant.customerId === mappingArray, + ), + ninjaName: mappingValue.label, + ninjaId: mappingValue.value, + }, + ]) + } + className={`my-4 circular-button`} + title={'+'} + > + + + + + + {(extensionNinjaOrgsAutomapResult.isSuccess || + extensionNinjaOrgsAutomapResult.isError) && + !extensionNinjaOrgsAutomapResult.isFetching && ( + + {extensionNinjaOrgsAutomapResult.isSuccess + ? extensionNinjaOrgsAutomapResult.data.Results + : 'Error'} + + )} + {(extensionNinjaOrgsConfigResult.isSuccess || + extensionNinjaOrgsConfigResult.isError) && + !extensionNinjaOrgsConfigResult.isFetching && ( + + {extensionNinjaOrgsConfigResult.isSuccess + ? extensionNinjaOrgsConfigResult.data.Results + : 'Error'} + + )} + + + + After editing the mappings you must click Save Mappings for the changes to + take effect. The table will be saved exactly as presented. + ) }} /> )} - - - - - NinjaOne Field Mapping Table - - + + + + + {extensionNinjaFieldsConfigResult.isFetching && ( + + )} + Set Mappings + + } + > {listBackendNinjaFieldsResult.isFetching ? ( ) : ( @@ -142,7 +519,7 @@ export function SettingsExtensionMappings() { initialValues={listBackendNinjaFieldsResult.data?.Mappings} render={({ handleSubmit, submitting, values }) => { return ( - +
Organization Global Custom Field Mapping

@@ -183,12 +560,6 @@ export function SettingsExtensionMappings() { ))} - - {extensionNinjaFieldsConfigResult.isFetching && ( - - )} - Set Mappings - {(extensionNinjaFieldsConfigResult.isSuccess || extensionNinjaFieldsConfigResult.isError) && !extensionNinjaFieldsConfigResult.isFetching && ( @@ -210,82 +581,9 @@ export function SettingsExtensionMappings() { }} /> )} - - - - - NinjaOne Organization Mapping Table - - - {listBackendNinjaOrgsResult.isFetching ? ( - - ) : ( - { - return ( - - - Use the table below to map your client to the correct NinjaOne Organization - {listBackendNinjaOrgsResult.isSuccess && - listBackendNinjaOrgsResult.data.Tenants.map((tenant) => ( - - ))} - - - - {extensionNinjaOrgsConfigResult.isFetching && ( - - )} - Set Mappings - - onNinjaOrgsAutomap()} className="me-2"> - {extensionNinjaOrgsAutomapResult.isFetching && ( - - )} - Automap NinjaOne Organizations - - {(extensionNinjaOrgsConfigResult.isSuccess || - extensionNinjaOrgsConfigResult.isError) && - !extensionNinjaFieldsConfigResult.isFetching && ( - - {extensionNinjaOrgsConfigResult.isSuccess - ? extensionNinjaOrgsConfigResult.data.Results - : 'Error'} - - )} - {(extensionNinjaOrgsAutomapResult.isSuccess || - extensionNinjaOrgsAutomapResult.isError) && ( - - {extensionNinjaOrgsAutomapResult.isSuccess - ? extensionNinjaOrgsAutomapResult.data.Results - : 'Error'} - - )} - - - ) - }} - /> - )} - - + + -

+ ) } diff --git a/src/views/cipp/app-settings/SettingsExtensions.jsx b/src/views/cipp/app-settings/SettingsExtensions.jsx index 44569219d704..fc728407ded3 100644 --- a/src/views/cipp/app-settings/SettingsExtensions.jsx +++ b/src/views/cipp/app-settings/SettingsExtensions.jsx @@ -20,6 +20,7 @@ import { RFFCFormInput, RFFCFormSwitch } from 'src/components/forms/index.js' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' import { CippCallout } from 'src/components/layout/index.js' +import CippButtonCard from 'src/components/contentcards/CippButtonCard' /** * Executes various operations related to settings and extensions. @@ -44,6 +45,39 @@ export function SettingsExtensions() { values: values, }) } + + const ButtonGenerate = (integrationType, forceSync) => ( + <> + + {extensionConfigResult.isFetching && ( + + )} + Set Extension Settings + + onSubmitTest(integrationType)} className="me-2"> + {listExtensionTestResult.isFetching && ( + + )} + Test Extension + + {forceSync && ( + + execSyncExtension({ + path: 'api/ExecExtensionSync?Extension=' + integrationType, + }) + } + className="me-2" + > + {listSyncExtensionResult.isFetching && ( + + )} + Force Sync + + )} + + ) + return (
{listBackendResult.isUninitialized && listBackend({ path: 'api/ListExtensionsConfig' })} @@ -74,106 +108,59 @@ export function SettingsExtensions() { {Extensions.map((integration, idx) => ( - - - {integration.name} - - -

{integration.helpText}

- { - return ( - - - - {integration.SettingOptions.map( - (integrationOptions, idx) => - integrationOptions.type === 'input' && ( - - - - ), - )} - {integration.SettingOptions.map( - (integrationOptions, idx) => - integrationOptions.type === 'checkbox' && ( - - - - ), - )} - - - - - - {extensionConfigResult.isFetching && ( - - )} - Set Extension Settings - - onSubmitTest(integration.type)} - className="me-2" - > - {listExtensionTestResult.isFetching && ( - - )} - Test Extension - - {integration.forceSyncButton && ( - - execSyncExtension({ - path: 'api/ExecExtensionSync?Extension=' + integration.type, - }) - } - className="me-2" - > - {listSyncExtensionResult.isFetching && ( - - )} - Force Sync - + +

{integration.helpText}

+ { + return ( + + + + {integration.SettingOptions.map( + (integrationOptions, idx) => + integrationOptions.type === 'input' && ( + + + + ), + )} + {integration.SettingOptions.map( + (integrationOptions, idx) => + integrationOptions.type === 'checkbox' && ( + + + + ), )} + - - ) - }} - /> -
-
+ + + ) + }} + /> +
))}
diff --git a/src/views/cipp/app-settings/SettingsGeneral.jsx b/src/views/cipp/app-settings/SettingsGeneral.jsx index db6494e17402..98a34210c72b 100644 --- a/src/views/cipp/app-settings/SettingsGeneral.jsx +++ b/src/views/cipp/app-settings/SettingsGeneral.jsx @@ -28,6 +28,7 @@ import { TableModalButton } from 'src/components/buttons/index.js' import { CippTable } from 'src/components/tables/index.js' import { TenantSelectorMultiple } from 'src/components/utilities/index.js' import { SettingsGeneralRow } from 'src/views/cipp/app-settings/components/SettingsGeneralRow.jsx' +import CippButtonCard from 'src/components/contentcards/CippButtonCard' /** * SettingsGeneral component. @@ -217,7 +218,43 @@ export function SettingsGeneral() { />, ], } + const permissionsCheckButton = ( + checkPermissions()} + disabled={permissionsResult.isFetching} + className="me-2" + > + {permissionsResult.isFetching && ( + + )} + Run Permissions Check + + ) + + const gdapButton = ( + checkGDAP({ path: '/api/ExecAccessChecks?GDAP=true' })} + disabled={GDAPResult.isFetching} + className="me-2" + > + {GDAPResult.isFetching && ( + + )} + Run GDAP Check + + ) + const tenantAccessCheckButton = ( + handleCheckAccess()} + disabled={accessCheckResult.isFetching || selectedTenants.length < 1} + > + {accessCheckResult.isFetching && ( + + )} + Run access check + + ) return (
@@ -227,218 +264,193 @@ export function SettingsGeneral() { - - - -

Permissions Check

-

Click the button below to start a permissions check.

- checkPermissions()} - disabled={permissionsResult.isFetching} - className="mb-3 me-2" - > - {permissionsResult.isFetching && ( - + +

Click the button below to start a permissions check.

+ + {permissionsResult.isSuccess && ( + <> + {permissionsResult.data.Results?.AccessTokenDetails?.Name !== '' && ( + <> + setTokenOffcanvasVisible(true)}> + Details + + setTokenOffcanvasVisible(false)} + /> + )} - Run Permissions Check -
- {permissionsResult.isSuccess && ( - <> - {permissionsResult.data.Results?.AccessTokenDetails?.Name !== '' && ( + + {permissionsResult.data.Results?.Messages && ( <> - setTokenOffcanvasVisible(true)}> - Details - - setTokenOffcanvasVisible(false)} - /> + {permissionsResult.data.Results?.Messages?.map((m, idx) => ( +
{m}
+ ))} )} - - {permissionsResult.data.Results?.Messages && ( - <> - {permissionsResult.data.Results?.Messages?.map((m, idx) => ( -
{m}
+ {permissionsResult.data.Results?.MissingPermissions.length > 0 && ( + <> + Your Secure Application Model is missing the following permissions. See the + documentation on how to add permissions{' '} + + here + + . + + {permissionsResult.data.Results?.MissingPermissions?.map((r, index) => ( + {r} ))} - - )} - {permissionsResult.data.Results?.MissingPermissions.length > 0 && ( - <> - Your Secure Application Model is missing the following permissions. See the - documentation on how to add permissions{' '} - - here - - . - - {permissionsResult.data.Results?.MissingPermissions?.map((r, index) => ( - {r} - ))} - - - )} -
- - )} -
-
+ + + )} + + + )} +
- - - -

GDAP Check

-

Click the button below to start a check for general GDAP settings.

- checkGDAP({ path: '/api/ExecAccessChecks?GDAP=true' })} - disabled={GDAPResult.isFetching} - className="mb-3 me-2" - > - {GDAPResult.isFetching && ( - - )} - Run GDAP Check - - {GDAPResult.isSuccess && ( - <> - p['@odata.type'] == '#microsoft.graph.group', - )} - title="Groups" - /> - p['@odata.type'] == '#microsoft.graph.directoryRole', - )} - title="Roles" - /> - - )} - - - {GDAPResult.isSuccess && GDAPResult.data.Results.GDAPIssues?.length > 0 && ( - <> - {GDAPResult.data.Results.GDAPIssues?.filter((e) => e.Type === 'Error') - .length > 0 && ( - - Relationship errors detected. Review the table below for more details. - - )} - {GDAPResult.data.Results.GDAPIssues?.filter((e) => e.Type === 'Warning') - .length > 0 && ( - - Relationship warnings detected. Review the table below for more details. - - )} - - + +

Click the button below to start a check for general GDAP settings.

+ + {GDAPResult.isSuccess && ( + <> + p['@odata.type'] == '#microsoft.graph.group', )} - {GDAPResult.isSuccess && GDAPResult.data.Results.GDAPIssues?.length === 0 && ( - - No relationships with issues found. Please perform a Permissions Check or - Tenant Access Check if you are experiencing issues. - + title="Groups" + /> + p['@odata.type'] == '#microsoft.graph.directoryRole', )} -
-
-
-
+ title="Roles" + /> + + )} + + + {GDAPResult.isSuccess && GDAPResult.data.Results.GDAPIssues?.length > 0 && ( + <> + {GDAPResult.data.Results.GDAPIssues?.filter((e) => e.Type === 'Error').length > + 0 && ( + + Relationship errors detected. Review the table below for more details. + + )} + {GDAPResult.data.Results.GDAPIssues?.filter((e) => e.Type === 'Warning') + .length > 0 && ( + + Relationship warnings detected. Review the table below for more details. + + )} + + + )} + {GDAPResult.isSuccess && GDAPResult.data.Results.GDAPIssues?.length === 0 && ( + + No relationships with issues found. Please perform a Permissions Check or Tenant + Access Check if you are experiencing issues. + + )} + + +
- - - -

Tenant Access Check

- - -
- Click the button below to start a tenant access check. You can select multiple, - but a maximum of {maxSelected + 1} tenants is recommended. -
+ + + +
+ Click the button below to start a tenant access check. You can select multiple, + but a maximum of {maxSelected + 1} tenants is recommended. +
- - handleSetSelectedTenants( - value.map((val) => { - return val.value - }), - ) - } - /> - {showMaxSelected && ( - - A maximum of {maxSelected + 1} tenants is recommended. - - )} -
-
+ + handleSetSelectedTenants( + value.map((val) => { + return val.value + }), + ) + } + /> + {showMaxSelected && ( + + A maximum of {maxSelected + 1} tenants is recommended. + + )} +
+
- - - handleCheckAccess()} - disabled={accessCheckResult.isFetching || selectedTenants.length < 1} - > - {accessCheckResult.isFetching && ( - - )} - Run access check - - - - - - {accessCheckResult.isSuccess && ( - - )} - - -
-
+ + + + + + {accessCheckResult.isSuccess && ( + + )} + + +
diff --git a/src/views/cipp/app-settings/SettingsNotifications.jsx b/src/views/cipp/app-settings/SettingsNotifications.jsx index 6a0b1b73450a..4dc512373b5a 100644 --- a/src/views/cipp/app-settings/SettingsNotifications.jsx +++ b/src/views/cipp/app-settings/SettingsNotifications.jsx @@ -4,21 +4,12 @@ import { } from 'src/store/api/app.js' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' -import { - CButton, - CCallout, - CCard, - CCardBody, - CCardHeader, - CCardTitle, - CCol, - CForm, - CSpinner, -} from '@coreui/react' +import { CButton, CCol, CForm, CSpinner } from '@coreui/react' import { Form, useForm } from 'react-final-form' import { RFFCFormInput, RFFCFormSwitch, RFFSelectSearch } from 'src/components/forms/index.js' import React from 'react' import { CippCallout } from 'src/components/layout/index.js' +import CippButtonCard from 'src/components/contentcards/CippButtonCard' /** * Sets the notification settings. @@ -32,151 +23,156 @@ export function SettingsNotifications() { configNotifications(values) } return ( - <> - {notificationListResult.isUninitialized && listNotification()} - {notificationListResult.isFetching && ( - - )} - {!notificationListResult.isFetching && notificationListResult.error && ( - Error loading data - )} - {notificationListResult.isSuccess && ( - - - Notifications - - - true} - initialValues={{ - ...notificationListResult.data, - logsToInclude: notificationListResult.data?.logsToInclude?.map((m) => ({ - label: m, - value: m, - })), - Severity: notificationListResult.data?.Severity?.map((s) => ({ - label: s, - value: s, - })), - }} - onSubmit={onSubmit} - render={({ handleSubmit, submitting, values }) => { - return ( - - {notificationConfigResult.isFetching && ( - - Loading - - )} - {notificationConfigResult.isSuccess && !notificationConfigResult.isFetching && ( - - {notificationConfigResult.data?.Results} - - )} - {notificationConfigResult.isError && !notificationConfigResult.isFetching && ( - - Could not connect to API: {notificationConfigResult.error.message} - - )} + + + Set Notification Settings + + } + isFetching={notificationListResult.isFetching} + > + {notificationListResult.isUninitialized && listNotification()} + {notificationListResult.isFetching && ( + + )} + {!notificationListResult.isFetching && notificationListResult.error && ( + Error loading data + )} + {notificationListResult.isSuccess && ( + true} + initialValues={{ + ...notificationListResult.data, + logsToInclude: notificationListResult.data?.logsToInclude?.map((m) => ({ + label: m, + value: m, + })), + Severity: notificationListResult.data?.Severity?.map((s) => ({ + label: s, + value: s, + })), + }} + onSubmit={onSubmit} + render={({ handleSubmit, submitting, values }) => { + return ( + + {notificationConfigResult.isFetching && ( + + Loading + + )} + {notificationConfigResult.isSuccess && !notificationConfigResult.isFetching && ( + + {notificationConfigResult.data?.Results} + + )} + {notificationConfigResult.isError && !notificationConfigResult.isFetching && ( + + Could not connect to API: {notificationConfigResult.error.message} + + )} + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - Set Notification Settings - + - - ) - }} - /> - - - )} - + + + ) + }} + /> + )} + + ) } diff --git a/src/views/cipp/app-settings/SettingsSuperAdmin.jsx b/src/views/cipp/app-settings/SettingsSuperAdmin.jsx index 41d8332387a3..7650089b5a23 100644 --- a/src/views/cipp/app-settings/SettingsSuperAdmin.jsx +++ b/src/views/cipp/app-settings/SettingsSuperAdmin.jsx @@ -1,20 +1,10 @@ import { useGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app.js' -import { - CButton, - CCallout, - CCard, - CCardBody, - CCardHeader, - CCol, - CForm, - CLink, - CRow, - CSpinner, -} from '@coreui/react' +import { CButton, CCol, CForm, CLink, CRow, CSpinner } from '@coreui/react' import { Form } from 'react-final-form' import { RFFCFormRadio } from 'src/components/forms/index.js' import React from 'react' import { CippCallout } from 'src/components/layout/index.js' +import CippButtonCard from 'src/components/contentcards/CippButtonCard' export function SettingsSuperAdmin() { const partnerConfig = useGenericGetRequestQuery({ @@ -30,84 +20,86 @@ export function SettingsSuperAdmin() { values: values, }).then((res) => {}) } + const buttonCard = ( + + {webhookCreateResult.isFetching ? ( + <> + + + ) : ( + 'Save' + )} + + ) return ( - - - + + <> <> - <> -

Super Admin Configuration

- - -

- The configuration settings below should only be modified by a super admin. Super - admins can configure what tenant mode CIPP operates in. See - - our documentation - - for more information on how to configure these modes and what they mean. -

-
-
- - -

Tenant Mode

- ( - <> - {partnerConfig.isFetching && } - - - - - - {webhookCreateResult.isFetching ? ( - <> - - Saving... - - ) : ( - 'Save' - )} - - - - )} - /> - {webhookCreateResult.isSuccess && ( - - {webhookCreateResult?.data?.results} - + + +

+ The configuration settings below should only be modified by a super admin. Super + admins can configure what tenant mode CIPP operates in. See + + our documentation + + for more information on how to configure these modes and what they mean. +

+
+
+ + +

Tenant Mode

+ ( + <> + {partnerConfig.isFetching && } + + + + + + )} -
-
- + /> + {webhookCreateResult.isSuccess && ( + + {webhookCreateResult?.data?.results} + + )} +
+
-
-
+ + ) } diff --git a/src/views/cipp/app-settings/components/SettingsDNSResolver.jsx b/src/views/cipp/app-settings/components/SettingsDNSResolver.jsx index a656578a185a..02067c5c8db2 100644 --- a/src/views/cipp/app-settings/components/SettingsDNSResolver.jsx +++ b/src/views/cipp/app-settings/components/SettingsDNSResolver.jsx @@ -2,6 +2,7 @@ import { CAlert, CButton, CButtonGroup } from '@coreui/react' import React, { useState } from 'react' import { useLazyEditDnsConfigQuery, useLazyGetDnsConfigQuery } from 'src/store/api/domains.js' import { CippCallout } from 'src/components/layout/index.js' +import CippButtonCard from 'src/components/contentcards/CippButtonCard' /** * Sets the DNS resolver based on user selection. @@ -17,34 +18,48 @@ export function SettingsDNSResolver() { await editDnsConfig({ resolver }) await getDnsConfig() } - + const cardbuttonGroup = ( + + {resolvers.map((resolver, index) => ( + switchResolver(resolver)} + color={resolver === getDnsConfigResult.data?.Resolver ? 'primary' : 'secondary'} + key={index} + > + {resolver} + + ))} + + ) return ( <> - {getDnsConfigResult.isUninitialized && getDnsConfig()} - {getDnsConfigResult.isSuccess && ( - <> -

DNS Resolver

- - {resolvers.map((resolver, index) => ( - switchResolver(resolver)} - color={resolver === getDnsConfigResult.data.Resolver ? 'primary' : 'secondary'} - key={index} - > - {resolver} - - ))} - - {(editDnsConfigResult.isSuccess || editDnsConfigResult.isError) && - !editDnsConfigResult.isFetching && ( - - {editDnsConfigResult.isSuccess - ? editDnsConfigResult.data.Results - : 'Error setting resolver'} - - )} - - )} + + {getDnsConfigResult.isUninitialized && getDnsConfig()} + {getDnsConfigResult.isSuccess && ( + <> + + Select your DNS Resolver. The DNS resolve is used for the domain analyser only, and + not for generic DNS resolution. + + {(editDnsConfigResult.isSuccess || editDnsConfigResult.isError) && + !editDnsConfigResult.isFetching && ( + + {editDnsConfigResult.isSuccess + ? editDnsConfigResult.data.Results + : 'Error setting resolver'} + + )} + + )} + ) } diff --git a/src/views/cipp/app-settings/components/SettingsGeneralRow.jsx b/src/views/cipp/app-settings/components/SettingsGeneralRow.jsx index 5e1f01f91e39..1e98f9ecfc8f 100644 --- a/src/views/cipp/app-settings/components/SettingsGeneralRow.jsx +++ b/src/views/cipp/app-settings/components/SettingsGeneralRow.jsx @@ -14,6 +14,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' import { SettingsPassword } from 'src/views/cipp/app-settings/components/SettingsPassword.jsx' import { SettingsDNSResolver } from 'src/views/cipp/app-settings/components/SettingsDNSResolver.jsx' +import CippButtonCard from 'src/components/contentcards/CippButtonCard' /** * Fetches and maintains DNS configuration settings for the application. @@ -26,7 +27,11 @@ export function SettingsGeneralRow() { const inputRef = useRef(null) const [clearCache, clearCacheResult] = useLazyExecClearCacheQuery() - const { data: versions, isSuccess: isSuccessVersion } = useLoadVersionsQuery() + const { + data: versions, + isSuccess: isSuccessVersion, + refetch: RefechVersion, + } = useLoadVersionsQuery() const downloadTxtFile = (data) => { const txtdata = [JSON.stringify(RunBackupResult.data.backup)] @@ -59,120 +64,158 @@ export function SettingsGeneralRow() { clearCache({ tenantsOnly: true }) }, }) + const refreshVersionButton = ( + RefechVersion()}>Check version update + ) + const cacheButton = ( + <> + handleClearCache()} + disabled={clearCacheResult.isFetching} + > + {clearCacheResult.isFetching && ( + + )} + Clear All Cache + + handleClearCacheTenant()} + disabled={clearCacheResult.isFetching} + > + {clearCacheResult.isFetching && ( + + )} + Clear Tenant Cache + + + ) + const backupButton = ( + <> + runBackup({ path: '/api/ExecRunBackup' })} + disabled={RunBackupResult.isFetching} + > + {RunBackupResult.isFetching && ( + + )} + Run backup + + inputRef.current.click()} + disabled={restoreBackupResult.isFetching} + > + {restoreBackupResult.isFetching && ( + + )} + Restore backup + + + ) return ( <> - - - - - - - - - - - -

Frontend Version

- + + + + + + + + + + +
Latest: {isSuccessVersion ? versions.RemoteCIPPVersion : }
-
- Current: {isSuccessVersion ? versions.LocalCIPPVersion : } -
-
- -

Clear Caches

- handleClearCache()} - disabled={clearCacheResult.isFetching} - > - {clearCacheResult.isFetching && ( - - )} - Clear All Cache - - handleClearCacheTenant()} - disabled={clearCacheResult.isFetching} - > - {clearCacheResult.isFetching && ( - - )} - Clear Tenant Cache - - {clearCacheResult.isSuccess && !clearCacheResult.isFetching && ( - - {clearCacheResult.data?.Results} - - )} -
- -

Settings Backup

- runBackup({ path: '/api/ExecRunBackup' })} - disabled={RunBackupResult.isFetching} +
Current: {isSuccessVersion ? versions.LocalCIPPVersion : }
+
+ +
+
+ + + + + Use this button to clear the caches used by CIPP. This will slow down some aspects of + the application, and should only be used when instructed to do so by support. + + {clearCacheResult.isSuccess && !clearCacheResult.isFetching && ( + - {RunBackupResult.isFetching && ( - - )} - Run backup - - handleChange(e)} - /> - inputRef.current.click()} - disabled={restoreBackupResult.isFetching} - > - {restoreBackupResult.isFetching && ( - - )} - Restore backup - - {restoreBackupResult.isSuccess && !restoreBackupResult.isFetching && ( - - {restoreBackupResult.data.Results} - - )} - {RunBackupResult.isSuccess && !restoreBackupResult.isFetching && ( - - downloadTxtFile(RunBackupResult.data.backup)}> - Download Backup - - - )} - - -

Backend API Version

- + {clearCacheResult.data?.Results} + + )} + +
+ + + handleChange(e)} + /> + + Use this button to backup the system configuration for CIPP. This will not include + authentication information or extension configuration. + + + {restoreBackupResult.isSuccess && !restoreBackupResult.isFetching && ( + + {restoreBackupResult.data.Results} + + )} + {RunBackupResult.isSuccess && !restoreBackupResult.isFetching && ( + + downloadTxtFile(RunBackupResult.data.backup)}> + Download Backup + + + )} + + + + + +
Latest: {isSuccessVersion ? versions.RemoteCIPPAPIVersion : }
-
- Current: {isSuccessVersion ? versions.LocalCIPPAPIVersion : } -
-
-
-
-
+
Current: {isSuccessVersion ? versions.LocalCIPPAPIVersion : }
+
+ + + ) } diff --git a/src/views/cipp/app-settings/components/SettingsPassword.jsx b/src/views/cipp/app-settings/components/SettingsPassword.jsx index aa8ed046d2b5..971f9b161a4b 100644 --- a/src/views/cipp/app-settings/components/SettingsPassword.jsx +++ b/src/views/cipp/app-settings/components/SettingsPassword.jsx @@ -1,7 +1,8 @@ import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app.js' import React, { useState } from 'react' -import { CButton, CButtonGroup, CCallout } from '@coreui/react' +import { CButton, CButtonGroup, CCallout, CCardText } from '@coreui/react' import { CippCallout } from 'src/components/layout/index.js' +import CippButtonCard from 'src/components/contentcards/CippButtonCard' /** * This method is responsible for handling password settings in the application. @@ -33,36 +34,50 @@ export function SettingsPassword() { } const resolvers = ['Classic', 'Correct-Battery-Horse'] - + const cardbuttonGroup = ( + + {resolvers.map((r, index) => ( + switchResolver(r)} + color={ + r === getPasswordConfigResult.data?.Results?.passwordType ? 'primary' : 'secondary' + } + key={index} + > + {r} + + ))} + + ) return ( <> - {getPasswordConfigResult.isUninitialized && - getPasswordConfig({ path: '/api/ExecPasswordConfig?list=true' })} -

Password Style

- - {resolvers.map((r, index) => ( - switchResolver(r)} - color={ - r === getPasswordConfigResult.data?.Results?.passwordType ? 'primary' : 'secondary' - } - key={index} - > - {r} - - ))} - - {(editPasswordConfigResult.isSuccess || editPasswordConfigResult.isError) && - !editPasswordConfigResult.isFetching && ( - - {editPasswordConfigResult.isSuccess - ? editPasswordConfigResult.data.Results - : 'Error setting password style'} - - )} + + {getPasswordConfigResult.isUninitialized && + getPasswordConfig({ path: '/api/ExecPasswordConfig?list=true' })} + + + Choose your password style. Classic passwords are a combination of letters and symbols. + Correct-Battery-Horse style is a passphrase, which is easier to remember and more secure + than classic passwords. + + + {(editPasswordConfigResult.isSuccess || editPasswordConfigResult.isError) && + !editPasswordConfigResult.isFetching && ( + + {editPasswordConfigResult.isSuccess + ? editPasswordConfigResult.data.Results + : 'Error setting password style'} + + )} + ) } diff --git a/src/views/email-exchange/connectors/DeployConnector.jsx b/src/views/email-exchange/connectors/DeployConnector.jsx index 16ebec88e10a..f2465f19d8ef 100644 --- a/src/views/email-exchange/connectors/DeployConnector.jsx +++ b/src/views/email-exchange/connectors/DeployConnector.jsx @@ -54,7 +54,6 @@ const DeployConnectorTemplate = () => { let template = EXConnectorTemplates.data.filter(function (obj) { return obj.GUID === value }) - // console.log(template[0][set]) onChange(JSON.stringify(template[0])) }} diff --git a/src/views/email-exchange/spamfilter/DeploySpamfilter.jsx b/src/views/email-exchange/spamfilter/DeploySpamfilter.jsx index 4a8c7e0e21b0..c45b21e3fb0d 100644 --- a/src/views/email-exchange/spamfilter/DeploySpamfilter.jsx +++ b/src/views/email-exchange/spamfilter/DeploySpamfilter.jsx @@ -54,7 +54,6 @@ const SpamFilterAdd = () => { let template = intuneTemplates.data.filter(function (obj) { return obj.GUID === value }) - // console.log(template[0][set]) onChange(JSON.stringify(template[0])) }} diff --git a/src/views/email-exchange/transport/DeployTransport.jsx b/src/views/email-exchange/transport/DeployTransport.jsx index 4ba34de996b4..8a0e5b6c35cb 100644 --- a/src/views/email-exchange/transport/DeployTransport.jsx +++ b/src/views/email-exchange/transport/DeployTransport.jsx @@ -54,7 +54,6 @@ const AddPolicy = () => { let template = TransportTemplates.data.filter(function (obj) { return obj.GUID === value }) - // console.log(template[0][set]) onChange(JSON.stringify(template[0])) }} diff --git a/src/views/endpoint/applications/ApplicationsAddWinGet.jsx b/src/views/endpoint/applications/ApplicationsAddWinGet.jsx index 525c39d93799..0880a67e3755 100644 --- a/src/views/endpoint/applications/ApplicationsAddWinGet.jsx +++ b/src/views/endpoint/applications/ApplicationsAddWinGet.jsx @@ -88,10 +88,8 @@ const AddWinGet = () => { {(value) => { let template = foundPackages.data.filter(function (obj) { - // console.log(value) return obj.packagename === value }) - //console.log(template[0]) onChange(template[0][set]) }} diff --git a/src/views/endpoint/autopilot/AutopilotAddDevice.jsx b/src/views/endpoint/autopilot/AutopilotAddDevice.jsx index 248f890b7e87..35abc7cc505f 100644 --- a/src/views/endpoint/autopilot/AutopilotAddDevice.jsx +++ b/src/views/endpoint/autopilot/AutopilotAddDevice.jsx @@ -93,7 +93,6 @@ const AddAPDevice = () => { } }) setAutopilotdata([...autopilotData, ...importdata]) - // console.log(importdata) } const handleOnError = (err, file, inputElem, reason) => { diff --git a/src/views/endpoint/intune/MEMAddPolicy.jsx b/src/views/endpoint/intune/MEMAddPolicy.jsx index 0b1de079d1e0..d742ba0c203a 100644 --- a/src/views/endpoint/intune/MEMAddPolicy.jsx +++ b/src/views/endpoint/intune/MEMAddPolicy.jsx @@ -215,7 +215,6 @@ const AddPolicy = () => { {(props) => { - console.log(props.values.RAWJson) const json = props.values?.RAWJson ? JSON.parse(props.values.RAWJson) : undefined return ( <> diff --git a/src/views/endpoint/intune/MEMListCompliance.jsx b/src/views/endpoint/intune/MEMListCompliance.jsx index c65a395997fd..6fbe0cdf84ce 100644 --- a/src/views/endpoint/intune/MEMListCompliance.jsx +++ b/src/views/endpoint/intune/MEMListCompliance.jsx @@ -19,7 +19,6 @@ import { cellBooleanFormatter, cellDateFormatter } from 'src/components/tables' const Actions = (row, rowIndex, formatExtraData) => { const [ocVisible, setOCVisible] = useState(false) - console.log(row) const tenant = useSelector((state) => state.app.currentTenant) return ( <> diff --git a/src/views/identity/administration/AddUserBulk.jsx b/src/views/identity/administration/AddUserBulk.jsx index 9b1e6dd3a507..bbf3805e5223 100644 --- a/src/views/identity/administration/AddUserBulk.jsx +++ b/src/views/identity/administration/AddUserBulk.jsx @@ -93,7 +93,6 @@ const AddUserBulk = () => { return item.data }) setBulkUser([...BulkUser, ...importdata]) - // console.log(importdata) } const handleOnError = (err, file, inputElem, reason) => { diff --git a/src/views/identity/administration/DeployGroupTemplate.jsx b/src/views/identity/administration/DeployGroupTemplate.jsx index f9bb9edfdb86..e86a70ff5228 100644 --- a/src/views/identity/administration/DeployGroupTemplate.jsx +++ b/src/views/identity/administration/DeployGroupTemplate.jsx @@ -60,7 +60,6 @@ const ApplyGroupTemplate = () => { let template = intuneTemplates.data.filter(function (obj) { return obj.GUID === value }) - // console.log(template[0][set]) onChange(template[0][set]) }} diff --git a/src/views/identity/administration/OffboardingWizard.jsx b/src/views/identity/administration/OffboardingWizard.jsx index 688be60b885f..e7ba79912958 100644 --- a/src/views/identity/administration/OffboardingWizard.jsx +++ b/src/views/identity/administration/OffboardingWizard.jsx @@ -133,7 +133,6 @@ const OffboardingWizard = () => { {/* eslint-disable react/prop-types */} {(props) => ( <> - {console.log(props.values)} {props.values.User?.length >= 3 && ( A maximum of three users is recommend. )} diff --git a/src/views/tenant/administration/GraphExplorer.jsx b/src/views/tenant/administration/GraphExplorer.jsx index 42cfc7f0e2da..bad378c27d3a 100644 --- a/src/views/tenant/administration/GraphExplorer.jsx +++ b/src/views/tenant/administration/GraphExplorer.jsx @@ -500,6 +500,11 @@ const GraphExplorer = () => { placeholder="Select the number of rows to return" /> + + { placeholder="Enter OData search query" /> + + diff --git a/src/views/tenant/administration/PartnerRelationships.jsx b/src/views/tenant/administration/PartnerRelationships.jsx new file mode 100644 index 000000000000..ea05fcb9e02b --- /dev/null +++ b/src/views/tenant/administration/PartnerRelationships.jsx @@ -0,0 +1,78 @@ +import React, { useEffect } from 'react' +import { useSelector } from 'react-redux' +import { CippPageList } from 'src/components/layout' +import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat' + +const PartnerRelationships = () => { + const tenant = useSelector((state) => state.app.currentTenant) + const [tenantColumnSet, setTenantColumn] = React.useState(false) + useEffect(() => { + if (tenant.defaultDomainName === 'AllTenants') { + setTenantColumn(false) + } + if (tenant.defaultDomainName !== 'AllTenants') { + setTenantColumn(true) + } + }, [tenant.defaultDomainName, tenantColumnSet]) + + const columns = [ + { + name: 'Tenant', + selector: (row) => row.Tenant, + sortable: true, + exportSelector: 'Tenant', + omit: tenantColumnSet, + cell: cellGenericFormatter(), + }, + { + name: 'Partner', + selector: (row) => row.TenantInfo?.displayName, + sortable: true, + exportSelector: 'TenantInfo/displayName', + cell: cellGenericFormatter(), + }, + { + name: 'Service Provider', + selector: (row) => row['isServiceProvider'], + sortable: true, + exportSelector: 'isServiceProvider', + cell: cellGenericFormatter(), + }, + { + name: 'Multi Tenant', + selector: (row) => row['isInMultiTenantOrganization'], + sortable: true, + exportSelector: 'isInMultiTenantOrganization', + cell: cellGenericFormatter(), + }, + { + name: 'Partner Info', + selector: (row) => row['TenantInfo'], + sortable: true, + exportSelector: 'TenantInfo', + cell: cellGenericFormatter(), + }, + ] + return ( +
+ +
+ ) +} + +export default PartnerRelationships diff --git a/src/views/tenant/administration/SecureScore.jsx b/src/views/tenant/administration/SecureScore.jsx new file mode 100644 index 000000000000..a99007b6a251 --- /dev/null +++ b/src/views/tenant/administration/SecureScore.jsx @@ -0,0 +1,435 @@ +import React, { useEffect, useRef } from 'react' +import { + CBadge, + CButton, + CCard, + CCardBody, + CCardFooter, + CCardHeader, + CCardText, + CCardTitle, + CCol, + CFormInput, + CFormSelect, + CFormSwitch, + CRow, +} from '@coreui/react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCheck, faTimes, faExclamation } from '@fortawesome/free-solid-svg-icons' +import { CippTable } from 'src/components/tables' +import { CippPage } from 'src/components/layout/CippPage' +import { useGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' +import { useSelector } from 'react-redux' +import Skeleton from 'react-loading-skeleton' +import standards from 'src/data/standards' +import { useNavigate } from 'react-router-dom' +import { ModalService } from 'src/components/utilities' +import { CellTip, cellGenericFormatter } from 'src/components/tables/CellGenericFormat' +import { CippCallout } from 'src/components/layout' + +const SecureScore = () => { + const textRef = useRef() + const selectRef = useRef() + const currentTenant = useSelector((state) => state.app.currentTenant) + const [viewMode, setViewMode] = React.useState(false) + const [translateData, setTranslatedData] = React.useState([]) + const [translateState, setTranslateSuccess] = React.useState(false) + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + const [refreshCode, setRefresh] = React.useState(null) + const { + data: securescore = [], + isFetching, + isSuccess, + } = useGenericGetRequestQuery({ + path: '/api/ListGraphRequest?refresh=' + refreshCode, + params: { + tenantFilter: currentTenant.defaultDomainName, + Endpoint: 'security/secureScores', + $top: 1, + NoPagination: true, + }, + }) + + const { + data: securescoreTranslation = [], + isSuccess: isSuccessTranslation, + isFetching: isFetchingTranslation, + } = useGenericGetRequestQuery({ + path: '/api/ListGraphRequest?refresh=' + refreshCode, + params: { + tenantFilter: currentTenant.defaultDomainName, + Endpoint: 'security/secureScoreControlProfiles', + $top: 999, + NoPagination: true, + }, + }) + + useEffect(() => { + if (isSuccess) { + setTranslatedData(securescore.Results[0]) + setTranslateSuccess(true) + } + }, [isSuccess, securescore.Results]) + + useEffect(() => { + if (isSuccess && isSuccessTranslation) { + const updatedControlScores = translateData.controlScores.map((control) => { + const translation = securescoreTranslation.Results?.find( + (controlTranslation) => controlTranslation.id === control.controlName, + ) + const remediation = standards.find((standard) => standard.tag.includes(control.controlName)) + return { + ...control, + title: translation?.title, + threats: translation?.threats, + complianceInformation: translation?.complianceInformation, + actionUrl: remediation + ? '/tenant/standards/list-applied-standards' + : translation?.actionUrl, + remediation: remediation + ? `1. Enable the CIPP Standard: ${remediation.label}` + : translation?.remediation, + remediationImpact: translation?.remediationImpact, + implementationCost: translation?.implementationCost, + tier: translation?.tier, + userImpact: translation?.userImpact, + vendorInformation: translation?.vendorInformation, + controlStateUpdates: translation?.controlStateUpdates[0] + ? translation.controlStateUpdates + : [], + } + }) + + updatedControlScores.sort((a, b) => { + return b['scoreInPercentage'] - a['scoreInPercentage'] + }) + setTranslatedData((prevData) => ({ + ...prevData, + controlScores: updatedControlScores, + })) + } + }, [isSuccess, isSuccessTranslation, securescoreTranslation.Results, refreshCode]) + const navigate = useNavigate() + + const openRemediation = (url) => { + if (url.startsWith('https')) { + window.open(url, '_blank') + } else { + navigate(url) + } + } + const openResolution = (control) => { + ModalService.confirm({ + key: control, + body: ( +
+
+ +
+
+ +
+
+ ), + title: 'Confirm', + onConfirm: () => + genericPostRequest({ + path: '/api/ExecUpdateSecureScore', + values: { + controlName: control.controlName, + resolutionType: selectRef.current.value, + reason: textRef.current.value, + tenantFilter: currentTenant.defaultDomainName, + vendorinformation: control.vendorInformation, + }, + }).then(() => { + setRefresh(Math.random()) + }), + }) + } + + const columns = [ + { + name: 'Task Title', + selector: (row) => row['title'], + sortable: true, + cell: (row) => CellTip(row['title']), + exportSelector: 'title', + }, + { + name: 'Percentage Complete', + selector: (row) => row['scoreInPercentage'], + sortable: true, + cell: (row) => CellTip(row['scoreInPercentage']), + exportSelector: 'scoreInPercentage', + }, + { + name: 'Remediation', + selector: (row) => row['actionUrl'], + sortable: true, + cell: cellGenericFormatter(), + exportSelector: 'actionUrl', + }, + ] + + return ( + <> + {postResults.isFetching && } + {postResults.isSuccess && ( + + {postResults.data.Results} + + )} + {postResults.isError && ( + + {postResults.error.message} + + )} + + + + + Overview mode + + + + setViewMode(!viewMode)} /> + + + + + + + + Current Score + + + + {isFetching && } + {translateState && ( + <> +

+ {Math.round( + (translateData?.currentScore / translateData?.maxScore) * 100 * 10, + ) / 10} + % +

+ + {translateData?.currentScore} of {translateData?.maxScore} points + + + )} +
+
+
+
+ + + + Compared Score (Similiar sized business) + + + + {isFetching && } + {translateState && ( + <> +

+ { + //calculate percentage, round to 1 dec. + Math.round( + (translateData?.averageComparativeScores[1]?.averageScore / + translateData?.maxScore) * + 100 * + 10, + ) / 10 + } + % +

+ + {translateData?.averageComparativeScores[1]?.averageScore} of{' '} + {translateData?.maxScore} points + + + )} +
+
+
+
+ + + + Compared Score (All businesses) + + + + {isFetching && } + {translateState && ( + <> +

+ { + //calculate percentage, round to 1 dec. + Math.round( + (translateData?.averageComparativeScores[0]?.averageScore / + translateData?.maxScore) * + 100 * + 10, + ) / 10 + } + % +

+ + {translateData?.averageComparativeScores[0]?.averageScore} of{' '} + {translateData?.maxScore} points + + + )} +
+
+
+
+
+ + {viewMode && translateData.controlScores.length > 1 && ( + + + Best Practice Report + + + + + + )} + {translateState && !viewMode && !isFetching && ( + <> + + {translateData?.controlScores?.map((info, idx) => ( + + + + {info.title} + + + + + + {info.scoreInPercentage === 100 + ? `100% ${ + info.controlStateUpdates?.length > 0 && + info.controlStateUpdates[0].state !== 'Default' + ? `(${info?.controlStateUpdates[0]?.state})` + : '' + }` + : `${info.scoreInPercentage}% ${ + info.controlStateUpdates?.length > 0 && + info.controlStateUpdates[0].state !== 'Default' + ? `(${info?.controlStateUpdates[0]?.state})` + : '' + } + `} + + + + +
Description
+ +
+ + + {info.scoreInPercentage !== 100 && ( + +
Remediation Recommendation
+ + { +
+ } + + + )} + + {info.threats?.length > 0 && ( + <> +
Threats
+ {info.threats?.map((threat, idx) => ( + + {threat} + + ))} + + )} +
+ + {info.complianceInformation > 0 && ( + <> +
Compliance Frameworks
+ {info.complianceInformation?.map((framework, idx) => ( + + {framework.certificationName} -{' '} + {framework.certificationControls[0]?.name} + + ))} + + )} +
+ + + openRemediation(info.actionUrl)} + className="me-3" + > + Remediate + + openResolution(info)} className="me-3"> + Change Status + + + + + ))} + + + )} + + + ) +} + +export default SecureScore diff --git a/src/views/tenant/administration/TenantOnboardingWizard.jsx b/src/views/tenant/administration/TenantOnboardingWizard.jsx index a6eb19460e55..122765dff6c3 100644 --- a/src/views/tenant/administration/TenantOnboardingWizard.jsx +++ b/src/views/tenant/administration/TenantOnboardingWizard.jsx @@ -312,14 +312,14 @@ const TenantOnboardingWizard = () => { reportName="Add-GDAP-Relationship" keyField="id" path="/api/ListGraphRequest" - params={{ Endpoint: 'tenantRelationships/delegatedAdminRelationships' }} + params={{ + Endpoint: 'tenantRelationships/delegatedAdminRelationships', + $filter: + "(status eq 'active' or status eq 'approvalPending') and not startsWith(displayName,'MLT_')", + }} columns={columns} filterlist={[ { filterName: 'Active Relationships', filter: 'Complex: status eq active' }, - { - filterName: 'Terminated Relationships', - filter: 'Complex: status eq terminated', - }, { filterName: 'Pending Relationships', filter: 'Complex: status eq approvalPending', diff --git a/src/views/tenant/conditional/DeployCA.jsx b/src/views/tenant/conditional/DeployCA.jsx index 46de880a02da..b68a928391de 100644 --- a/src/views/tenant/conditional/DeployCA.jsx +++ b/src/views/tenant/conditional/DeployCA.jsx @@ -61,7 +61,6 @@ const AddPolicy = () => { let template = intuneTemplates.data.filter(function (obj) { return obj.GUID === value }) - // console.log(template[0][set]) onChange(JSON.stringify(template[0])) }} diff --git a/src/views/tenant/standards/ListAppliedStandards.jsx b/src/views/tenant/standards/ListAppliedStandards.jsx index 8a280ca6f830..3745c099aec7 100644 --- a/src/views/tenant/standards/ListAppliedStandards.jsx +++ b/src/views/tenant/standards/ListAppliedStandards.jsx @@ -69,7 +69,6 @@ const DeleteAction = () => { } const ApplyNewStandard = () => { const [templateStandard, setTemplateStandard] = useState() - console.log(templateStandard) const RefreshAction = () => { const [execStandards, execStandardsResults] = useLazyGenericGetRequestQuery() const {