diff --git a/src/data/Extensions.json b/src/data/Extensions.json
index bc556899e4a8..e46565493f28 100644
--- a/src/data/Extensions.json
+++ b/src/data/Extensions.json
@@ -1,6 +1,6 @@
[
{
- "name": "CIPP-API Integration",
+ "name": "CIPP-API",
"type": "CIPP-API",
"cat": "API",
"forceSyncButton": false,
@@ -21,7 +21,7 @@
"mappingRequired": false
},
{
- "name": "Gradient Integration",
+ "name": "Gradient",
"type": "Gradient",
"cat": "Billing & Invoicing",
"forceSyncButton": true,
@@ -55,7 +55,7 @@
"mappingRequired": false
},
{
- "name": "Halo PSA Ticketing Integration",
+ "name": "Halo PSA Ticketing",
"type": "HaloPSA",
"cat": "Ticketing",
"forceSyncButton": false,
@@ -112,7 +112,7 @@
"mappingRequired": true
},
{
- "name": "NinjaOne Integration",
+ "name": "NinjaOne",
"type": "NinjaOne",
"cat": "Documentation & Monitoring",
"forceSyncButton": true,
@@ -141,18 +141,18 @@
},
{
"type": "checkbox",
- "name": "NinjaOne.UserDocumentsEnabled",
- "label": "Synchronize Detailed User Information (Requires NinjaOne Documentation)"
+ "name": "NinjaOne.LicenseDocumentsEnabled",
+ "label": "Sync Licenses (Requires NinjaOne Documentation)"
},
{
"type": "checkbox",
- "name": "NinjaOne.LicenseDocumentsEnabled",
- "label": "Synchronize Detailed License Information (Requires NinjaOne Documentation)"
+ "name": "NinjaOne.UserDocumentsEnabled",
+ "label": "Sync Users (Requires NinjaOne Documentation)"
},
{
"type": "checkbox",
"name": "NinjaOne.LicensedOnly",
- "label": "Only Synchronize Licensed Users"
+ "label": "Only Sync Licensed Users (Requires NinjaOne Documentation)"
},
{
"type": "checkbox",
@@ -160,7 +160,44 @@
"label": "Enable Integration"
}
],
- "mappingRequired": true
+ "mappingRequired": true,
+ "fieldMapping": true,
+ "autoMapSyncApi": true
+ },
+ {
+ "name": "Hudu",
+ "type": "Hudu",
+ "cat": "Documentation",
+ "forceSyncButton": true,
+ "helpText": "This integration allows you to populate custom asset layouts with Tenant information, monitor device compliance state, document other items and generate relationships inside Hudu.",
+ "SettingOptions": [
+ {
+ "type": "input",
+ "fieldtype": "input",
+ "name": "Hudu.BaseUrl",
+ "label": "Please enter your Hudu URL",
+ "placeholder": "https://yourcompany.huducloud.com"
+ },
+ {
+ "type": "input",
+ "fieldtype": "password",
+ "name": "Hudu.APIKey",
+ "label": "Hudu API Key",
+ "placeholder": "Enter your Hudu API Key"
+ },
+ {
+ "type": "checkbox",
+ "name": "Hudu.LicensedUsersOnly",
+ "label": "Only Sync Licensed Users"
+ },
+ {
+ "type": "checkbox",
+ "name": "Hudu.Enabled",
+ "label": "Enable Integration"
+ }
+ ],
+ "mappingRequired": true,
+ "fieldMapping": true
},
{
"name": "PasswordPusher",
diff --git a/src/views/cipp/ExtensionMappings.jsx b/src/views/cipp/ExtensionMappings.jsx
new file mode 100644
index 000000000000..131d04bece52
--- /dev/null
+++ b/src/views/cipp/ExtensionMappings.jsx
@@ -0,0 +1,412 @@
+import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app.js'
+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, { useEffect } from 'react'
+import { CippCallout } from 'src/components/layout/index.js'
+import { CippTable } from 'src/components/tables'
+import { CellTip, cellGenericFormatter } 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 default function ExtensionMappings({ type, fieldMappings = false, autoMapSyncApi = false }) {
+ const [mappingArray, setMappingArray] = React.useState('defaultMapping')
+ const [mappingValue, setMappingValue] = React.useState({})
+ const [tenantMappingArray, setTenantMappingsArray] = React.useState([])
+ const [autoMap, setAutoMap] = React.useState(false)
+ const [listMappingBackend, listMappingBackendResult = []] = useLazyGenericGetRequestQuery()
+ const [listFieldsBackend, listFieldsBackendResult] = useLazyGenericGetRequestQuery()
+ const [setExtensionConfig, extensionConfigResult = []] = useLazyGenericPostRequestQuery()
+ const [setExtensionAutomap, extensionAutomapResult] = useLazyGenericPostRequestQuery()
+ const [setFieldsExtensionConfig, extensionFieldsConfigResult] = useLazyGenericPostRequestQuery()
+
+ const onOrgSubmit = () => {
+ console.log(mappingArray)
+ const originalFormat = mappingArray.reduce((acc, item) => {
+ acc[item.Tenant?.customerId] = { label: item.companyName, value: item.companyId }
+ return acc
+ }, {})
+ setExtensionConfig({
+ path: `api/ExecExtensionMapping?AddMapping=${type}`,
+ values: { mappings: originalFormat },
+ }).then(() => {
+ listMappingBackend({ path: `api/ExecExtensionMapping?List=${type}` })
+ setMappingValue({})
+ })
+ }
+ /*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: originalFormat },
+ }).then(() => {
+ listNinjaOrgsBackend({ path: 'api/ExecExtensionMapping?List=NinjaOrgs' })
+ setMappingValue({})
+ })
+ }*/
+
+ const onOrgsAutomap = async (values) => {
+ if (autoMapSyncApi) {
+ await setExtensionAutomap({
+ path: `api/ExecExtensionMapping?AutoMapping=${type}`,
+ values: { mappings: values },
+ })
+ await listMappingBackend({
+ path: `api/ExecExtensionMapping?List=${type}`,
+ })
+ }
+
+ const newMappings = listMappingBackendResult.data?.Tenants.map((tenant) => {
+ const company = listMappingBackendResult.data?.Companies.find(
+ (client) => client.name === tenant.displayName,
+ )
+ if (company) {
+ return {
+ Tenant: tenant,
+ companyName: company.name,
+ companyId: company.value,
+ }
+ }
+ })
+ setMappingArray((currentMappings) => [...currentMappings, ...newMappings])
+ setAutoMap(true)
+ }
+
+ const onFieldsSubmit = (values) => {
+ setFieldsExtensionConfig({
+ path: `api/ExecExtensionMapping?AddMapping=${type}Fields`,
+ 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]).then(
+ () => {
+ listHaloBackend({ path: 'api/ExecExtensionMapping?List=Halo' })
+ },
+ )
+ setHaloAutoMap(true)
+ }*/
+
+ useEffect(() => {
+ if (listMappingBackendResult.isSuccess) {
+ setMappingArray(
+ Object.keys(listMappingBackendResult.data?.Mappings).map((key) => ({
+ Tenant: listMappingBackendResult.data?.Tenants.find(
+ (tenant) => tenant.customerId === key,
+ ),
+ companyName: listMappingBackendResult.data?.Mappings[key].label,
+ companyId: listMappingBackendResult.data?.Mappings[key].value,
+ })),
+ )
+ }
+ }, [listMappingBackendResult])
+
+ const Actions = (row, rowIndex, formatExtraData) => {
+ return (
+ <>
+
+
+ setMappingArray((currentMappings) => currentMappings.filter((item) => item !== row))
+ }
+ >
+
+
+
+ >
+ )
+ }
+ const columns = [
+ {
+ 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: `${type} Company Name`,
+ selector: (row) => row['companyName'],
+ sortable: true,
+ cell: cellGenericFormatter(),
+ exportSelector: 'companyName',
+ },
+ {
+ name: `${type} Company ID`,
+ selector: (row) => row['companyId'],
+ sortable: true,
+ cell: (row) => CellTip(row['companyId']),
+ exportSelector: 'companyId',
+ },
+ {
+ name: 'Actions',
+ cell: Actions,
+ maxWidth: '80px',
+ },
+ ]
+
+ return (
+
+ <>
+ {listMappingBackendResult.isUninitialized &&
+ listMappingBackend({ path: `api/ExecExtensionMapping?List=${type}` })}
+ {listFieldsBackendResult.isUninitialized &&
+ fieldMappings &&
+ listFieldsBackend({ path: `api/ExecExtensionMapping?List=${type}Fields` })}
+
+
+ {extensionConfigResult.isFetching && (
+
+ )}
+ Set Mappings
+
+ onOrgsAutomap()} className="me-2">
+ {extensionAutomapResult.isFetching && (
+
+ )}
+ Automap {type} Organizations
+
+ >
+ }
+ >
+ {listMappingBackendResult.isFetching && listMappingBackendResult.isUninitialized ? (
+
+ ) : (
+
+ >
+ {fieldMappings && (
+
+ {extensionFieldsConfigResult.isFetching && (
+
+ )}
+ Set Mappings
+
+ }
+ >
+ {listFieldsBackendResult.isFetching && listFieldsBackendResult.isUninitialized && (
+
+ )}
+ {listFieldsBackendResult.isSuccess && listFieldsBackendResult.data?.Mappings && (
+
+ )}
+
+ )
+}
diff --git a/src/views/cipp/Extensions.jsx b/src/views/cipp/Extensions.jsx
index 8a165cbc0dfd..d08ac0636174 100644
--- a/src/views/cipp/Extensions.jsx
+++ b/src/views/cipp/Extensions.jsx
@@ -21,7 +21,7 @@ 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'
+import ExtensionMappings from 'src/views/cipp/ExtensionMappings.jsx'
export default function CIPPExtensions() {
const [listBackend, listBackendResult] = useLazyGenericGetRequestQuery()
@@ -191,9 +191,15 @@ export default function CIPPExtensions() {
)}
-
-
-
+ {integration.mappingRequired && (
+
+
+
+ )}