diff --git a/src/_nav.jsx b/src/_nav.jsx index 902a8390b79f..04fa3f3866a1 100644 --- a/src/_nav.jsx +++ b/src/_nav.jsx @@ -75,6 +75,11 @@ const _nav = [ name: 'Roles', to: '/identity/administration/roles', }, + { + component: CNavItem, + name: 'JIT Admin', + to: '/identity/administration/users/jit-admin', + }, { component: CNavItem, name: 'Offboarding Wizard', @@ -771,6 +776,11 @@ const _nav = [ name: 'Application Settings', to: '/cipp/settings', }, + { + component: CNavItem, + name: 'Extensions Settings', + to: '/cipp/extensions', + }, { component: CNavItem, name: 'User Settings', diff --git a/src/components/forms/RFFComponents.jsx b/src/components/forms/RFFComponents.jsx index 01b4ed8f083b..b72a840d4989 100644 --- a/src/components/forms/RFFComponents.jsx +++ b/src/components/forms/RFFComponents.jsx @@ -587,7 +587,11 @@ export const RFFSelectSearch = ({ {...props} /> )} - {meta.error && meta.touched && {meta.error}} + {meta.error && meta.touched && ( + + {typeof meta.error === 'object' ? Object.values(meta.error).join('') : meta.error} + + )} ) }} diff --git a/src/data/Extensions.json b/src/data/Extensions.json index 0f26e93f26a5..98a5c9a5a3f2 100644 --- a/src/data/Extensions.json +++ b/src/data/Extensions.json @@ -16,7 +16,8 @@ "name": "cippapi.Enabled", "label": "Enable Integration" } - ] + ], + "mappingRequired": false }, { "name": "Gradient Integration", @@ -49,7 +50,8 @@ "name": "Gradient.Enabled", "label": "Enable Integration" } - ] + ], + "mappingRequired": false }, { "name": "Halo PSA Ticketing Integration", @@ -105,7 +107,8 @@ "name": "HaloPSA.Enabled", "label": "Enable Integration" } - ] + ], + "mappingRequired": true }, { "name": "NinjaOne Integration", @@ -155,6 +158,7 @@ "name": "NinjaOne.Enabled", "label": "Enable Integration" } - ] + ], + "mappingRequired": true } ] diff --git a/src/importsMap.jsx b/src/importsMap.jsx index fd44014ae9c7..d42693788afb 100644 --- a/src/importsMap.jsx +++ b/src/importsMap.jsx @@ -12,6 +12,7 @@ import React from 'react' "/identity/administration/users/edit": React.lazy(() => import('./views/identity/administration/EditUser')), "/identity/administration/users/view": React.lazy(() => import('./views/identity/administration/ViewUser')), "/identity/administration/users/InviteGuest": React.lazy(() => import('./views/identity/administration/InviteGuest')), + "/identity/administration/users/jit-admin": React.lazy(() => import('./views/identity/administration/DeployJITAdmin')), "/identity/administration/ViewBec": React.lazy(() => import('./views/identity/administration/ViewBEC')), "/identity/administration/users": React.lazy(() => import('./views/identity/administration/Users')), "/identity/administration/devices": React.lazy(() => import('./views/identity/administration/Devices')), @@ -129,6 +130,7 @@ import React from 'react' "/security/reports/list-device-compliance": React.lazy(() => import('./views/security/reports/ListDeviceComplianceReport')), "/license": React.lazy(() => import('./views/pages/license/License')), "/cipp/settings": React.lazy(() => import('./views/cipp/app-settings/CIPPSettings')), + "/cipp/extensions": React.lazy(() => import('./views/cipp/Extensions')), "/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')), diff --git a/src/routes.json b/src/routes.json index e681555e0346..61c736f9c23d 100644 --- a/src/routes.json +++ b/src/routes.json @@ -76,6 +76,12 @@ "component": "views/identity/administration/InviteGuest", "allowedRoles": ["admin", "editor", "readonly"] }, + { + "path": "/identity/administration/users/jit-admin", + "name": "JIT Admin", + "component": "views/identity/administration/DeployJITAdmin", + "allowedRoles": ["admin", "editor", "readonly"] + }, { "path": "/identity/administration/ViewBec", "name": "View BEC", @@ -888,6 +894,12 @@ "component": "views/cipp/app-settings/CIPPSettings", "allowedRoles": ["admin"] }, + { + "path": "/cipp/extensions", + "name": "Extensions Settings", + "component": "views/cipp/Extensions", + "allowedRoles": ["admin"] + }, { "path": "/cipp/setup", "name": "Setup", diff --git a/src/views/cipp/Extensions.jsx b/src/views/cipp/Extensions.jsx new file mode 100644 index 000000000000..d42e79d9bb38 --- /dev/null +++ b/src/views/cipp/Extensions.jsx @@ -0,0 +1,173 @@ +import React, { useRef, useState } from 'react' +import { + CButton, + CCardText, + CCol, + CForm, + CNav, + CNavItem, + CRow, + CTabContent, + CTabPane, +} from '@coreui/react' +import { CippPage } from 'src/components/layout' +import { CippLazy } from 'src/components/utilities' +import { useNavigate } from 'react-router-dom' +import useQuery from 'src/hooks/useQuery.jsx' +import Extensions from 'src/data/Extensions.json' +import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app.js' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' +import CippButtonCard from 'src/components/contentcards/CippButtonCard.jsx' +import { RFFCFormInput, RFFCFormSwitch } from 'src/components/forms/RFFComponents.jsx' +import { Form } from 'react-final-form' +import { SettingsExtensionMappings } from './app-settings/SettingsExtensionMappings' + +export default function CIPPExtensions() { + const [listBackend, listBackendResult] = useLazyGenericGetRequestQuery() + const inputRef = useRef(null) + const [setExtensionconfig, extensionConfigResult] = useLazyGenericPostRequestQuery() + const [execTestExtension, listExtensionTestResult] = useLazyGenericGetRequestQuery() + const [execSyncExtension, listSyncExtensionResult] = useLazyGenericGetRequestQuery() + + const onSubmitTest = (integrationName) => { + execTestExtension({ + path: 'api/ExecExtensionTest?extensionName=' + integrationName, + }) + } + const onSubmit = (values) => { + setExtensionconfig({ + path: 'api/ExecExtensionsConfig', + 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 + + )} + + ) + const queryString = useQuery() + const navigate = useNavigate() + + const tab = queryString.get('tab') + const [active, setActiveTab] = useState(tab ? parseInt(tab) : 0) + const setActive = (tab) => { + setActiveTab(tab) + queryString.set('tab', tab.toString()) + navigate(`${location.pathname}?${queryString}`) + } + + return ( + + {listBackendResult.isUninitialized && listBackend({ path: 'api/ListExtensionsConfig' })} + + {Extensions.map((integration, idx) => ( + setActive(idx)} + href="#" + > + {integration.name} + + ))} + + + {Extensions.map((integration, idx) => ( + + + + + +

{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/CIPPSettings.jsx b/src/views/cipp/app-settings/CIPPSettings.jsx index 965e1f98f11f..14e5c3e0cb9e 100644 --- a/src/views/cipp/app-settings/CIPPSettings.jsx +++ b/src/views/cipp/app-settings/CIPPSettings.jsx @@ -58,14 +58,8 @@ export default function CIPPSettings() { setActive(7)} href="#"> Maintenance - setActive(8)} href="#"> - Extensions - - setActive(9)} href="#"> - Extension Mappings - {superAdmin && ( - setActive(10)} href="#"> + setActive(8)} href="#"> SuperAdmin Settings )} @@ -106,16 +100,6 @@ export default function CIPPSettings() { - - - - - - - - - - diff --git a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx index 33d7491795bf..6d7a63d2a3d0 100644 --- a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx +++ b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx @@ -19,13 +19,14 @@ import { CippCallout } from 'src/components/layout/index.js' import CippAccordionItem from 'src/components/contentcards/CippAccordionItem' import { CippTable } from 'src/components/tables' import { CellTip } from 'src/components/tables/CellGenericFormat' +import CippButtonCard from 'src/components/contentcards/CippButtonCard' /** * Retrieves and sets the extension mappings for HaloPSA and NinjaOne. * * @returns {JSX.Element} - JSX component representing the settings extension mappings. */ -export function SettingsExtensionMappings() { +export function SettingsExtensionMappings({ type }) { const [addedAttributes, setAddedAttribute] = React.useState(1) const [mappingArray, setMappingArray] = React.useState('defaultMapping') const [mappingValue, setMappingValue] = React.useState({}) @@ -242,310 +243,326 @@ export function SettingsExtensionMappings() { return ( - {listBackendHaloResult.isUninitialized && - listHaloBackend({ path: 'api/ExecExtensionMapping?List=Halo' })} - {listBackendNinjaOrgsResult.isUninitialized && - listNinjaOrgsBackend({ path: 'api/ExecExtensionMapping?List=NinjaOrgs' })} - {listBackendNinjaFieldsResult.isUninitialized && - listNinjaFieldsBackend({ path: 'api/ExecExtensionMapping?List=NinjaFields' })} - - - - {extensionHaloConfigResult.isFetching && ( - - )} - Save Mappings - - onHaloAutomap()} className="me-2"> - {extensionNinjaOrgsAutomapResult.isFetching && ( - - )} - Automap HaloPSA Clients - - - } - > - {listBackendHaloResult.isFetching && listBackendHaloResult.isUninitialized ? ( - - ) : ( - { - return ( - - - 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 && ( - - ) - } - - - { - return !Object.keys(listBackendHaloResult.data?.Mappings).includes( - tenant.customerId, - ) - }).map((tenant) => ({ - name: tenant.displayName, - value: tenant.customerId, - }))} - onChange={(e) => { - setMappingArray(e.value) + {type === 'HaloPSA' && ( + <> + {listBackendHaloResult.isUninitialized && + listHaloBackend({ path: 'api/ExecExtensionMapping?List=Halo' })} + + + + {extensionHaloConfigResult.isFetching && ( + + )} + Save Mappings + + onHaloAutomap()} className="me-2"> + {extensionNinjaOrgsAutomapResult.isFetching && ( + + )} + Automap HaloPSA Clients + + + } + > + {listBackendHaloResult.isFetching && listBackendHaloResult.isUninitialized ? ( + + ) : ( + { + return ( + + + 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 && ( + + ) + } + + + { + return !Object.keys(listBackendHaloResult.data?.Mappings).includes( + tenant.customerId, + ) + }).map((tenant) => ({ + name: tenant.displayName, + value: tenant.customerId, + }))} + onChange={(e) => { + setMappingArray(e.value) + }} + isLoading={listBackendHaloResult.isFetching} + /> + + + + + + { + return !Object.values(listBackendHaloResult.data?.Mappings) + .map((value) => { + return value.value + }) + .includes(client.value) + }).map((client) => ({ + name: client.name, + value: client.value, + }))} + onChange={(e) => setMappingValue(e)} + placeholder="Select a HaloPSA Client" + isLoading={listBackendHaloResult.isFetching} + /> + + { + if ( + mappingValue.value !== undefined && + mappingValue.value !== '-1' && + Object.values(haloMappingsArray) + .map((item) => item.haloId) + .includes(mappingValue.value) === false + ) { + //set the new mapping in the array + setHaloMappingsArray([ + ...haloMappingsArray, + { + Tenant: listBackendHaloResult.data?.Tenants.find( + (tenant) => tenant.customerId === mappingArray, + ), + haloName: mappingValue.label, + haloId: mappingValue.value, + }, + ]) + } }} - isLoading={listBackendHaloResult.isFetching} - /> - - - - - - { - return !Object.values(listBackendHaloResult.data?.Mappings) - .map((value) => { - return value.value - }) - .includes(client.value) - }).map((client) => ({ - name: client.name, - value: client.value, - }))} - onChange={(e) => setMappingValue(e)} - placeholder="Select a HaloPSA Client" - isLoading={listBackendHaloResult.isFetching} - /> - - { - if ( - mappingValue.value !== undefined && - mappingValue.value !== '-1' && - Object.values(haloMappingsArray) - .map((item) => item.haloId) - .includes(mappingValue.value) === false - ) { - //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={'+'} - > - - - - - - {HaloAutoMap && ( - - Automapping has been executed. Remember to check the changes and save - them. - - )} - {(extensionHaloConfigResult.isSuccess || extensionHaloConfigResult.isError) && - !extensionHaloConfigResult.isFetching && ( - - {extensionHaloConfigResult.isSuccess - ? extensionHaloConfigResult.data.Results - : 'Error'} - + + + + + + {HaloAutoMap && ( + + Automapping has been executed. Remember to check the changes and save + them. + )} - - - - 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 && listBackendNinjaOrgsResult.isUninitialized ? ( - - ) : ( - { - 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 && ( - - ) - } - - - { - return !Object.keys( - listBackendNinjaOrgsResult.data?.Mappings, - ).includes(tenant.customerId) - }).map((tenant) => ({ - name: tenant.displayName, - value: tenant.customerId, - }))} - onChange={(e) => { - setMappingArray(e.value) - }} - isLoading={listBackendNinjaOrgsResult.isFetching} - /> - - - - - - { - return !Object.values(listBackendNinjaOrgsResult.data?.Mappings) - .map((value) => { - return value.value - }) - .includes(client.value.toString()) - }).map((client) => ({ - name: client.name, - value: client.value, - }))} - onChange={(e) => setMappingValue(e)} - placeholder="Select a NinjaOne Organization" - isLoading={listBackendNinjaOrgsResult.isFetching} - /> - - { - //set the new mapping in the array - if ( - mappingValue.value !== undefined && - mappingValue.value !== '-1' && - Object.values(ninjaMappingsArray) - .map((item) => item.ninjaId) - .includes(mappingValue.value) === false - ) { - setNinjaMappingsArray([ - ...ninjaMappingsArray, - { - Tenant: listBackendNinjaOrgsResult.data?.Tenants.find( - (tenant) => tenant.customerId === mappingArray, - ), - ninjaName: mappingValue.label, - ninjaId: mappingValue.value, + {(extensionHaloConfigResult.isSuccess || + extensionHaloConfigResult.isError) && + !extensionHaloConfigResult.isFetching && ( + + {extensionHaloConfigResult.isSuccess + ? extensionHaloConfigResult.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. + + + ) + }} + /> + )} + + + )} + {type === 'NinjaOne' && ( + <> + {listBackendNinjaOrgsResult.isUninitialized && + listNinjaOrgsBackend({ path: 'api/ExecExtensionMapping?List=NinjaOrgs' })} + {listBackendNinjaFieldsResult.isUninitialized && + listNinjaFieldsBackend({ path: 'api/ExecExtensionMapping?List=NinjaFields' })} + + + {extensionNinjaOrgsConfigResult.isFetching && ( + + )} + Set Mappings + + onNinjaOrgsAutomap()} className="me-2"> + {extensionNinjaOrgsAutomapResult.isFetching && ( + + )} + Automap NinjaOne Organizations + + + } + > + {listBackendNinjaOrgsResult.isFetching && listBackendNinjaOrgsResult.isUninitialized ? ( + + ) : ( + { + 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 && ( + + ) + } + + + { + return !Object.keys( + listBackendNinjaOrgsResult.data?.Mappings, + ).includes(tenant.customerId) + }).map((tenant) => ({ + name: tenant.displayName, + value: tenant.customerId, + }))} + onChange={(e) => { + setMappingArray(e.value) + }} + isLoading={listBackendNinjaOrgsResult.isFetching} + /> + + + + + + { + return !Object.values(listBackendNinjaOrgsResult.data?.Mappings) + .map((value) => { + return value.value + }) + .includes(client.value.toString()) }, - ]) - } - }} - className={`my-4 circular-button`} - title={'+'} - > - - - - - - {(extensionNinjaOrgsAutomapResult.isSuccess || - extensionNinjaOrgsAutomapResult.isError) && - !extensionNinjaOrgsAutomapResult.isFetching && ( - - {extensionNinjaOrgsAutomapResult.isSuccess - ? extensionNinjaOrgsAutomapResult.data.Results - : 'Error'} - - )} - {(extensionNinjaOrgsConfigResult.isSuccess || - extensionNinjaOrgsConfigResult.isError) && - !extensionNinjaOrgsConfigResult.isFetching && ( - ({ + name: client.name, + value: client.value, + }))} + onChange={(e) => setMappingValue(e)} + placeholder="Select a NinjaOne Organization" + isLoading={listBackendNinjaOrgsResult.isFetching} + /> + + { + //set the new mapping in the array + if ( + mappingValue.value !== undefined && + mappingValue.value !== '-1' && + Object.values(ninjaMappingsArray) + .map((item) => item.ninjaId) + .includes(mappingValue.value) === false + ) { + setNinjaMappingsArray([ + ...ninjaMappingsArray, + { + Tenant: listBackendNinjaOrgsResult.data?.Tenants.find( + (tenant) => tenant.customerId === mappingArray, + ), + ninjaName: mappingValue.label, + ninjaId: mappingValue.value, + }, + ]) + } + }} + className={`my-4 circular-button`} + title={'+'} > - {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. - - - ) - }} - /> - )} - - + + + + + {(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. + + + ) + }} + /> + )} + + + )} + {type === 'NinjaOne' && ( + )} - - + + )} ) } diff --git a/src/views/cipp/app-settings/SettingsExtensions.jsx b/src/views/cipp/app-settings/SettingsExtensions.jsx index 20ebbcee9725..ab686d2c1df9 100644 --- a/src/views/cipp/app-settings/SettingsExtensions.jsx +++ b/src/views/cipp/app-settings/SettingsExtensions.jsx @@ -1,20 +1,6 @@ import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app.js' import React, { useRef } from 'react' -import { - CAccordion, - CAlert, - CButton, - CCallout, - CCard, - CCardBody, - CCardHeader, - CCardText, - CCardTitle, - CCol, - CForm, - CRow, - CSpinner, -} from '@coreui/react' +import { CAccordion, CButton, CCardText, CCol, CForm, CSpinner } from '@coreui/react' import Extensions from 'src/data/Extensions.json' import { Form } from 'react-final-form' import { RFFCFormInput, RFFCFormSwitch } from 'src/components/forms/index.js' diff --git a/src/views/cipp/app-settings/SettingsSuperAdmin.jsx b/src/views/cipp/app-settings/SettingsSuperAdmin.jsx index bec964fa3386..4e38038fb68c 100644 --- a/src/views/cipp/app-settings/SettingsSuperAdmin.jsx +++ b/src/views/cipp/app-settings/SettingsSuperAdmin.jsx @@ -6,6 +6,7 @@ import React from 'react' import { CippCallout } from 'src/components/layout/index.js' import CippAccordionItem from 'src/components/contentcards/CippAccordionItem' import SettingsCustomRoles from 'src/views/cipp/app-settings/components/SettingsCustomRoles' +import CippButtonCard from 'src/components/contentcards/CippButtonCard' export function SettingsSuperAdmin() { const partnerConfig = useGenericGetRequestQuery({ @@ -39,8 +40,8 @@ export function SettingsSuperAdmin() { ) return ( - - + - - - - - + + + ) } diff --git a/src/views/cipp/app-settings/components/SettingsCustomRoles.jsx b/src/views/cipp/app-settings/components/SettingsCustomRoles.jsx index 6f439a01c832..75c0a7504487 100644 --- a/src/views/cipp/app-settings/components/SettingsCustomRoles.jsx +++ b/src/views/cipp/app-settings/components/SettingsCustomRoles.jsx @@ -21,6 +21,7 @@ import PropTypes from 'prop-types' import { OnChange } from 'react-final-form-listeners' import { useListTenantsQuery } from 'src/store/api/tenants' import CippListOffcanvas, { OffcanvasListSection } from 'src/components/utilities/CippListOffcanvas' +import CippButtonCard from 'src/components/contentcards/CippButtonCard' const SettingsCustomRoles = () => { const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() @@ -29,6 +30,8 @@ const SettingsCustomRoles = () => { const { data: tenants = [], tenantsFetching } = useListTenantsQuery({ showAllTenantSelector: true, }) + const [allTenantSelected, setAllTenantSelected] = useState(false) + const [cippApiRoleSelected, setCippApiRoleSelected] = useState(false) const { data: apiPermissions = [], @@ -47,6 +50,20 @@ const SettingsCustomRoles = () => { path: 'api/ExecCustomRole', }) + const handleTenantChange = (e) => { + var alltenant = false + e.map((tenant) => { + if (tenant.value === 'AllTenants') { + alltenant = true + } + }) + if (alltenant) { + setAllTenantSelected(true) + } else { + setAllTenantSelected(false) + } + setSelectedTenant(e) + } const handleSubmit = async (values) => { //filter on only objects that are 'true' genericPostRequest({ @@ -91,6 +108,12 @@ const SettingsCustomRoles = () => { let customRole = customRoleList.filter(function (obj) { return obj.RowKey === value.value }) + if (customRole[0]?.RowKey === 'CIPP-API') { + setCippApiRoleSelected(true) + } else { + setCippApiRoleSelected(false) + } + if (customRole === undefined || customRole === null || customRole.length === 0) { return false } else { @@ -219,7 +242,7 @@ const SettingsCustomRoles = () => { } return ( - + <>

Custom roles can be used to restrict permissions for users with the 'editor' or 'readonly' @@ -227,10 +250,10 @@ const SettingsCustomRoles = () => { direct API access, create a role with the name 'CIPP-API'.

- NOTE: The custom role must be added to the user in SWA in conjunction with the base role. - (e.g. editor,mycustomrole) + This functionality is in + beta and should be treated as such. The custom role must be added to the user in SWA in + conjunction with the base role. (e.g. editor,mycustomrole)

- {(isFetching || tenantsFetching) && } {isSuccess && !isFetching && !tenantsFetching && ( { /> + {cippApiRoleSelected && ( + + This role will limit access for the CIPP-API integration. It is not + intended to be used for users. + + )}
Allowed Tenants
@@ -262,8 +291,13 @@ const SettingsCustomRoles = () => { values={selectedTenant} AllTenants={true} valueIsDomain={true} - onChange={(e) => setSelectedTenant(e)} + onChange={(e) => handleTenantChange(e)} /> + {allTenantSelected && ( + + All tenants selected, no tenant restrictions will be applied. + + )}
API Permissions
@@ -324,6 +358,7 @@ const SettingsCustomRoles = () => { + {({ values }) => { @@ -401,7 +436,7 @@ const SettingsCustomRoles = () => { /> )} -
+ ) } diff --git a/src/views/identity/administration/DeployJITAdmin.jsx b/src/views/identity/administration/DeployJITAdmin.jsx new file mode 100644 index 000000000000..126150f95360 --- /dev/null +++ b/src/views/identity/administration/DeployJITAdmin.jsx @@ -0,0 +1,226 @@ +import React, { useState } from 'react' +import { CButton, CCallout, CCol, CForm, CRow, CSpinner, CTooltip } from '@coreui/react' +import { useSelector } from 'react-redux' +import { Field, Form, FormSpy } from 'react-final-form' +import { + Condition, + RFFCFormInput, + RFFCFormRadio, + RFFCFormRadioList, + RFFCFormSwitch, + RFFSelectSearch, +} from 'src/components/forms' +import { + useGenericGetRequestQuery, + useLazyGenericGetRequestQuery, + useLazyGenericPostRequestQuery, +} from 'src/store/api/app' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCircleNotch, faEdit, faEye } from '@fortawesome/free-solid-svg-icons' +import { CippContentCard, CippPage, CippPageList } from 'src/components/layout' +import { CellTip } from 'src/components/tables/CellGenericFormat' +import 'react-datepicker/dist/react-datepicker.css' +import { CippActionsOffcanvas, ModalService, TenantSelector } from 'src/components/utilities' +import arrayMutators from 'final-form-arrays' +import DatePicker from 'react-datepicker' +import 'react-datepicker/dist/react-datepicker.css' +import { useListUsersQuery } from 'src/store/api/users' +import { useListConditionalAccessPoliciesQuery } from 'src/store/api/tenants' +import GDAPRoles from 'src/data/GDAPRoles' + +const DeployJITAdmin = () => { + const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery() + const currentDate = new Date() + const [startDate, setStartDate] = useState(currentDate) + const [endDate, setEndDate] = useState(currentDate) + + const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) + const [refreshState, setRefreshState] = useState(false) + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + + const onSubmit = (values) => { + const startTime = Math.floor(startDate.getTime() / 1000) + const endTime = Math.floor(endDate.getTime() / 1000) + const shippedValues = { + tenantFilter: tenantDomain, + UserId: values.UserId?.value, + PolicyId: values.PolicyId?.value, + StartDate: startTime, + EndDate: endTime, + ExpireAction: values?.expireAction ?? 'delete', + } + genericPostRequest({ path: '/api/ExecJITAdmin', values: shippedValues }).then((res) => { + setRefreshState(res.requestId) + }) + } + + const { + data: users = [], + isFetching: usersIsFetching, + error: usersError, + } = useListUsersQuery({ tenantDomain }) + + return ( + + <> + + + + { + return ( + +

+ JIT Admin creates an account that is usable for a specific period of time. + Enter a username, select admin roles, date range and expiration action. +

+ + + + {(props) => } + + + +
+
+ + + + + + + + + + + + + + + + ({ + value: user.id, + name: `${user.displayName} <${user.userPrincipalName}>`, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="UserId" + isLoading={usersIsFetching} + /> + + + + + + ({ + value: role.ObjectId, + name: role.Name, + }))} + multi={true} + placeholder="Select Roles" + name="AdminRoles" + /> + + + + + + setStartDate(date)} + /> + + + + setEndDate(date)} + /> + + + + + + + + + + + Add JIT Admin + {postResults.isFetching && ( + + )} + + + + {postResults.isSuccess && ( + +
  • {postResults.data.Results}
  • +
    + )} + {getResults.isFetching && ( + + Loading + + )} + {getResults.isSuccess && ( + {getResults.data?.Results} + )} + {getResults.isError && ( + + Could not connect to API: {getResults.error.message} + + )} +
    + ) + }} + /> +
    +
    +
    + +
    + ) +} + +export default DeployJITAdmin diff --git a/src/views/tenant/administration/AlertWizard.jsx b/src/views/tenant/administration/AlertWizard.jsx index 2c3bf20bf0cf..549a316beb1f 100644 --- a/src/views/tenant/administration/AlertWizard.jsx +++ b/src/views/tenant/administration/AlertWizard.jsx @@ -96,7 +96,6 @@ const AlertWizard = () => { const getRecurrenceOptions = () => { const values = currentFormState?.values if (values) { - //console.log(currentFormState) const updatedRecurrenceOptions = recurrenceOptions.map((opt) => ({ ...opt, name: opt.name.replace(' (Recommended)', ''), @@ -317,7 +316,7 @@ const AlertWizard = () => { multi={true} name={`actions`} placeholder={ - 'Select one action or multple actions from the list' + 'Select one action or multiple actions from the list' } label="Then perform the following action(s)" /> diff --git a/src/views/tenant/standards/BestPracticeAnalyser.jsx b/src/views/tenant/standards/BestPracticeAnalyser.jsx index 10e7c480c29f..801cd1eb6838 100644 --- a/src/views/tenant/standards/BestPracticeAnalyser.jsx +++ b/src/views/tenant/standards/BestPracticeAnalyser.jsx @@ -346,99 +346,115 @@ const BestPracticeAnalyser = () => { refreshFunction={setRefreshValue} /> - {graphrequest.data.Columns.map((info, idx) => ( - - - - {info.name} - - - - {info.formatter === 'bool' && ( - - - {graphrequest.data.Data[0][info.value] ? 'Yes' : 'No'} - - )} - {info.formatter === 'reverseBool' && ( - - - {graphrequest.data.Data[0][info.value] ? 'No' : 'Yes'} - - )} - {info.formatter === 'warnBool' && ( - - - {graphrequest.data.Data[0][info.value] ? 'Yes' : 'No'} - - )} + {graphrequest.data?.Data[0] && + Object.keys(graphrequest.data.Data[0]).length === 0 ? ( + + + Best Practice Report + + + + No Data Found for this tenant. Please refresh the tenant data. + + + + ) : ( + graphrequest.data.Columns.map((info, idx) => ( + + + + {info.name} + + + + {info.formatter === 'bool' && ( + + + {graphrequest.data.Data[0][info.value] ? 'Yes' : 'No'} + + )} + {info.formatter === 'reverseBool' && ( + + + {graphrequest.data.Data[0][info.value] ? 'No' : 'Yes'} + + )} + {info.formatter === 'warnBool' && ( + + + {graphrequest.data.Data[0][info.value] ? 'Yes' : 'No'} + + )} - {info.formatter === 'table' && ( - <> - - - )} + {info.formatter === 'table' && ( + <> + + + )} - {info.formatter === 'number' && ( -

    - {getNestedValue(graphrequest.data.Data[0], info.value)} -

    - )} -
    - - {info.desc} - -
    -
    -
    - ))} + {info.formatter === 'number' && ( +

    + {getNestedValue(graphrequest.data.Data[0], info.value)} +

    + )} +
    + + {info.desc} + +
    +
    +
    + )) + )} )} diff --git a/src/views/tenant/standards/ListAppliedStandards.jsx b/src/views/tenant/standards/ListAppliedStandards.jsx index 484ec3862a36..c48dd3705753 100644 --- a/src/views/tenant/standards/ListAppliedStandards.jsx +++ b/src/views/tenant/standards/ListAppliedStandards.jsx @@ -634,20 +634,13 @@ const ApplyNewStandard = () => {
    Remediate