Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NinjaOne Integration #1905

Merged
merged 6 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added src/assets/images/ninjaone.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/components/layout/AppFooter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import huntressLogo from 'src/assets/images/huntress_teal.png'
import dattoLogo from 'src/assets/images/datto.png'
import rewstLogo from 'src/assets/images/rewst.png'
import netfriends from 'src/assets/images/netfriends.png'
import ninjaLogo from 'src/assets/images/ninjaone.png'
//todo: Add darkmode detection and change logos accordingly.
const AppFooter = () => {
return (
Expand All @@ -24,6 +25,9 @@ const AppFooter = () => {
<CLink href="https://netfriends.com">
<CImage src={netfriends} alt="Netfriends" />
</CLink>
KelvinTegelaar marked this conversation as resolved.
Show resolved Hide resolved
<CLink href="https://ninjaone.com">
<CImage src={ninjaLogo} alt="NinjaOne" />
</CLink>
</p>
</div>
<nav className="footer-nav">
Expand Down
50 changes: 50 additions & 0 deletions src/data/Extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,55 @@
"label": "Enable Integration"
}
]
},
{
"name": "NinjaOne Integration",
"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.",
"SettingOptions": [
{
"type": "input",
"fieldtype": "input",
"name": "NinjaOne.Instance",
"label": "Please enter your NinjaOne Instance",
"placeholder": "app.ninjarmm.com, eu.ninjarmm.com, oc.ninjarmm.com, ca.ninjarmm.com, us2.ninjarmm.com"
},
{
"type": "input",
"fieldtype": "password",
"name": "NinjaOne.ClientID",
"label": "NinjaOne API Client ID",
"placeholder": "Enter your NinjaOne API Client ID"
},
{
"type": "input",
"fieldtype": "password",
"name": "NinjaOne.APIKey",
"label": "NinjaOne API Client Secret",
"placeholder": "Enter your NinjaOne API Client Secret"
},
{
"type": "checkbox",
"name": "NinjaOne.UserDocumentsEnabled",
"label": "Synchronize Detailed User Information (Requires NinjaOne Documentation)"
},
{
"type": "checkbox",
"name": "NinjaOne.LicenseDocumentsEnabled",
"label": "Synchronize Detailed License Information (Requires NinjaOne Documentation)"
},
{
"type": "checkbox",
"name": "NinjaOne.LicensedOnly",
"label": "Only Synchronize Licensed Users"
},
{
"type": "checkbox",
"name": "NinjaOne.Enabled",
"label": "Enable Integration"
}
]
}
]
184 changes: 184 additions & 0 deletions src/views/cipp/CIPPSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1766,18 +1766,54 @@ const ExtensionsTab = () => {

const MappingsTab = () => {
const [listHaloBackend, listBackendHaloResult = []] = useLazyGenericGetRequestQuery()
const [listNinjaOrgsBackend, listBackendNinjaOrgsResult] = useLazyGenericGetRequestQuery()
const [listNinjaFieldsBackend, listBackendNinjaFieldsResult] = useLazyGenericGetRequestQuery()
const [setHaloExtensionconfig, extensionHaloConfigResult = []] = useLazyGenericPostRequestQuery()
const [setNinjaOrgsExtensionconfig, extensionNinjaOrgsConfigResult] =
useLazyGenericPostRequestQuery()
const [setNinjaOrgsExtensionAutomap, extensionNinjaOrgsAutomapResult] =
useLazyGenericPostRequestQuery()
const [setNinjaFieldsExtensionconfig, extensionNinjaFieldsConfigResult] =
useLazyGenericPostRequestQuery()

const onHaloSubmit = (values) => {
setHaloExtensionconfig({
path: 'api/ExecExtensionMapping?AddMapping=Halo',
values: { mappings: values },
})
}
const onNinjaOrgsSubmit = (values) => {
setNinjaOrgsExtensionconfig({
path: 'api/ExecExtensionMapping?AddMapping=NinjaOrgs',
values: { mappings: values },
})
}

const onNinjaOrgsAutomap = async (values) => {
await setNinjaOrgsExtensionAutomap({
path: 'api/ExecExtensionMapping?AutoMapping=NinjaOrgs',
values: { mappings: values },
})
await listNinjaOrgsBackend({
path: 'api/ExecExtensionMapping?List=NinjaOrgs',
})
}

const onNinjaFieldsSubmit = (values) => {
setNinjaFieldsExtensionconfig({
path: 'api/ExecExtensionMapping?AddMapping=NinjaFields',

values: { mappings: values },
})
}
return (
<div>
{listBackendHaloResult.isUninitialized &&
listHaloBackend({ path: 'api/ExecExtensionMapping?List=Halo' })}
{listBackendNinjaOrgsResult.isUninitialized &&
listNinjaOrgsBackend({ path: 'api/ExecExtensionMapping?List=NinjaOrgs' })}
{listBackendNinjaFieldsResult.isUninitialized &&
listNinjaFieldsBackend({ path: 'api/ExecExtensionMapping?List=NinjaFields' })}
<>
<CCard className="mb-3">
<CCardHeader>
Expand Down Expand Up @@ -1831,6 +1867,154 @@ const MappingsTab = () => {
)}
</CCardBody>
</CCard>
<CCard className="mb-3">
<CCardHeader>
<CCardTitle>NinjaOne Field Mapping Table</CCardTitle>
</CCardHeader>
<CCardBody>
{listBackendNinjaFieldsResult.isFetching ? (
<CSpinner color="primary" />
) : (
<Form
onSubmit={onNinjaFieldsSubmit}
initialValues={listBackendNinjaFieldsResult.data?.Mappings}
render={({ handleSubmit, submitting, values }) => {
return (
<CForm onSubmit={handleSubmit}>
<CCardText>
<h5>Organization Global Custom Field Mapping</h5>
<p>
Use the table below to map your Organization Field to the correct NinjaOne
Field
</p>
{listBackendNinjaFieldsResult.isSuccess &&
listBackendNinjaFieldsResult.data.CIPPOrgFields.map((CIPPOrgFields) => (
<RFFSelectSearch
key={CIPPOrgFields.InternalName}
name={CIPPOrgFields.InternalName}
label={CIPPOrgFields.Type + ' - ' + CIPPOrgFields.Description}
values={listBackendNinjaFieldsResult.data.NinjaOrgFields.filter(
(item) => item.type === CIPPOrgFields.Type || item.type === 'unset',
)}
placeholder="Select a Field"
/>
))}
</CCardText>
<CCardText>
<h5>Device Custom Field Mapping</h5>
<p>
Use the table below to map your Device field to the correct NinjaOne
WYSIWYG Field
</p>
{listBackendNinjaFieldsResult.isSuccess &&
listBackendNinjaFieldsResult.data.CIPPNodeFields.map((CIPPNodeFields) => (
<RFFSelectSearch
key={CIPPNodeFields.InternalName}
name={CIPPNodeFields.InternalName}
label={CIPPNodeFields.Type + ' - ' + CIPPNodeFields.Description}
values={listBackendNinjaFieldsResult.data.NinjaNodeFields.filter(
(item) =>
item.type === CIPPNodeFields.Type || item.type === 'unset',
)}
placeholder="Select a Field"
/>
))}
</CCardText>
<CCol className="me-2">
<CButton className="me-2" type="submit">
{extensionNinjaFieldsConfigResult.isFetching && (
<FontAwesomeIcon icon={faCircleNotch} spin className="me-2" size="1x" />
)}
Set Mappings
</CButton>
{(extensionNinjaFieldsConfigResult.isSuccess ||
extensionNinjaFieldsConfigResult.isError) && (
<CCallout
color={
extensionNinjaFieldsConfigResult.isSuccess ? 'success' : 'danger'
}
>
{extensionNinjaFieldsConfigResult.isSuccess
? extensionNinjaFieldsConfigResult.data.Results
: 'Error'}
</CCallout>
)}
</CCol>
</CForm>
)
}}
/>
)}
</CCardBody>
</CCard>
<CCard className="mb-3">
<CCardHeader>
<CCardTitle>NinjaOne Organization Mapping Table</CCardTitle>
</CCardHeader>
<CCardBody>
{listBackendNinjaOrgsResult.isFetching ? (
<CSpinner color="primary" />
) : (
<Form
onSubmit={onNinjaOrgsSubmit}
initialValues={listBackendNinjaOrgsResult.data?.Mappings}
render={({ handleSubmit, submitting, values }) => {
return (
<CForm onSubmit={handleSubmit}>
<CCardText>
Use the table below to map your client to the correct NinjaOne Organization
{listBackendNinjaOrgsResult.isSuccess &&
listBackendNinjaOrgsResult.data.Tenants.map((tenant) => (
<RFFSelectSearch
key={tenant.customerId}
name={tenant.customerId}
label={tenant.displayName}
values={listBackendNinjaOrgsResult.data.NinjaOrgs}
placeholder="Select an Organization"
/>
))}
</CCardText>
<CCol className="me-2">
<CButton className="me-2" type="submit">
{extensionNinjaOrgsConfigResult.isFetching && (
<FontAwesomeIcon icon={faCircleNotch} spin className="me-2" size="1x" />
)}
Set Mappings
</CButton>
<CButton onClick={() => onNinjaOrgsAutomap()} className="me-2">
{extensionNinjaOrgsAutomapResult.isFetching && (
<FontAwesomeIcon icon={faCircleNotch} spin className="me-2" size="1x" />
)}
Automap NinjaOne Organizations
</CButton>
{(extensionNinjaOrgsConfigResult.isSuccess ||
extensionNinjaOrgsConfigResult.isError) && (
<CCallout
color={extensionNinjaOrgsConfigResult.isSuccess ? 'success' : 'danger'}
>
{extensionNinjaOrgsConfigResult.isSuccess
? extensionNinjaOrgsConfigResult.data.Results
: 'Error'}
</CCallout>
)}
{(extensionNinjaOrgsAutomapResult.isSuccess ||
extensionNinjaOrgsAutomapResult.isError) && (
<CCallout
color={extensionNinjaOrgsAutomapResult.isSuccess ? 'success' : 'danger'}
>
{extensionNinjaOrgsAutomapResult.isSuccess
? extensionNinjaOrgsAutomapResult.data.Results
: 'Error'}
</CCallout>
)}
</CCol>
</CForm>
)
}}
/>
)}
</CCardBody>
</CCard>
</>
</div>
)
Expand Down
Loading