From 298e1bcd9197d816ac684705a2bcc5c0e32fa9a3 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 3 Apr 2024 19:09:18 +0200 Subject: [PATCH 001/130] add jsx --- src/App.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.jsx b/src/App.jsx index d10d00cf6434..922a083050e5 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -13,7 +13,7 @@ import routes from 'src/routes' import { useAuthCheck } from './components/utilities/CippauthCheck' library.add(fas) -const dynamicImport = (path) => React.lazy(() => import(`./${path}`)) +const dynamicImport = (path) => React.lazy(() => import(`./${path}.jsx`)) const DefaultLayout = React.lazy(() => import('./layout/DefaultLayout')) const Page401 = React.lazy(() => import('./views/pages/page401/Page401')) const Page403 = React.lazy(() => import('./views/pages/page403/Page403')) From b1cf7e2d5d41296ef120279c712795606dd0a0e0 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 3 Apr 2024 19:40:57 +0200 Subject: [PATCH 002/130] potential fix. --- src/App.jsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 922a083050e5..1b2db0d6e9ec 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -13,7 +13,8 @@ import routes from 'src/routes' import { useAuthCheck } from './components/utilities/CippauthCheck' library.add(fas) -const dynamicImport = (path) => React.lazy(() => import(`./${path}.jsx`)) + +const dynamicImport = (path) => React.lazy(() => import(`./${path}`)) const DefaultLayout = React.lazy(() => import('./layout/DefaultLayout')) const Page401 = React.lazy(() => import('./views/pages/page401/Page401')) const Page403 = React.lazy(() => import('./views/pages/page403/Page403')) @@ -22,6 +23,14 @@ const Page500 = React.lazy(() => import('./views/pages/page500/Page500')) const PageLogOut = React.lazy(() => import('src/views/pages/LogoutRedirect/PageLogOut')) const Login = React.lazy(() => import('./views/pages/login/Login')) const Logout = React.lazy(() => import('./views/pages/login/Logout')) +//we loop through the routes array, dynamicly create the component by using dynamicImport, add the component to the route array as 'component' key. + +routes.forEach((route) => { + if (route.component) { + route['component'] = dynamicImport(route.component) + } +}) + const App = () => { return ( @@ -46,10 +55,9 @@ const App = () => { } > {routes.map((route, idx) => { - const Component = dynamicImport(route.component) const allowedRoles = route.allowedRoles return ( - Component && ( + route.component && ( { CIPP - {route.name} - + From 1d172a510429f314f83925f2a3deefd23a381a9a Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 3 Apr 2024 22:19:12 +0200 Subject: [PATCH 003/130] Fixes dynamic routes issues. --- .prettierignore | 3 + Generate-Import-Map.js | 35 +++++++++++ Importmap.ps1 | 22 +++++++ package.json | 1 + src/App.jsx | 17 ++--- src/importsMap.jsx | 138 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 208 insertions(+), 8 deletions(-) create mode 100644 Generate-Import-Map.js create mode 100644 Importmap.ps1 create mode 100644 src/importsMap.jsx diff --git a/.prettierignore b/.prettierignore index 849ddff3b7ec..1611c469b3e9 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,4 @@ dist/ +build/ +importsMap.jsx +Generate-Import-Map.js \ No newline at end of file diff --git a/Generate-Import-Map.js b/Generate-Import-Map.js new file mode 100644 index 000000000000..d8b23547c55b --- /dev/null +++ b/Generate-Import-Map.js @@ -0,0 +1,35 @@ +// Using ES Module syntax compatible with Node.js 18 and ensuring cross-platform compatibility +import fs from 'fs/promises' +import path from 'path' +import { fileURLToPath } from 'url' + +// Convert __dirname equivalent for ES Modules +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +// Adjust the relative path as necessary to point to your routes.json location +const routesPath = path.join(__dirname, './src/routes.json') // Example path + +// Load routes.json with an import assertion for JSON +const routes = await import(`file://${routesPath}`, { assert: { type: 'json' } }).then( + (module) => module.default, +) + +let importsMap = "import React from 'react'\n export const importsMap = {\n" + +routes.forEach((route) => { + if (route.component) { + // Adjust the import path to be relative to the importsMap.js file location + const importPath = route.component.replace('views', './views') + // Ensure paths are Unix-like for the dynamic import to work cross-platform + const unixImportPath = importPath.split(path.sep).join('/') + importsMap += ` "${route.path}": React.lazy(() => import('${unixImportPath}')), \n` + } +}) + +importsMap += '}\nexport default importsMap' + +// Specify the output file path for the generated imports map +const outputPath = path.join(__dirname, './src/importsMap.jsx') +await fs.writeFile(outputPath, importsMap) +console.log('Import map generated.') diff --git a/Importmap.ps1 b/Importmap.ps1 new file mode 100644 index 000000000000..357c3af5d1ce --- /dev/null +++ b/Importmap.ps1 @@ -0,0 +1,22 @@ +// generate-imports-map.js +const fs = require('fs') +const path = require('path') +const routes = require('./path/to/routes.json') + +let importsMap = 'export const importsMap = {\n' + +routes.forEach(route => { + if (route.component) { + // Convert the path to a format that's relative to where you'll be importing from + const importPath = route.component.replace('views', './views') + const componentName = path.basename(importPath) + + // Create an import statement for the component + importsMap += "${route.path}": React.lazy(() = > import('${importPath}')), \n`; + } + }) + +importsMap += '};\n' + +fs.writeFileSync(path.resolve(__dirname,'./src/importsMap.js'), importsMap) +console.log('Import map generated.') diff --git a/package.json b/package.json index 3a1237c0f6c0..042efe701871 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "license": "AGPL-3.0", "author": "CIPP Contributors", "scripts": { + "prebuild": "node Generate-Import-Map.js", "build": "echo react-scripts build && vite build", "changelog": "auto-changelog --starting-version 3.0.0 --commit-limit false --hide-credit", "lint": "eslint \"src/**/*.js\"", diff --git a/src/App.jsx b/src/App.jsx index 1b2db0d6e9ec..5e5678b80784 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -11,10 +11,14 @@ import { library } from '@fortawesome/fontawesome-svg-core' import { fas } from '@fortawesome/free-solid-svg-icons' import routes from 'src/routes' import { useAuthCheck } from './components/utilities/CippauthCheck' +import importsMap from './importsMap' library.add(fas) -const dynamicImport = (path) => React.lazy(() => import(`./${path}`)) +const dynamicImport = (path) => { + return importsMap[path] || null +} + const DefaultLayout = React.lazy(() => import('./layout/DefaultLayout')) const Page401 = React.lazy(() => import('./views/pages/page401/Page401')) const Page403 = React.lazy(() => import('./views/pages/page403/Page403')) @@ -25,12 +29,6 @@ const Login = React.lazy(() => import('./views/pages/login/Login')) const Logout = React.lazy(() => import('./views/pages/login/Logout')) //we loop through the routes array, dynamicly create the component by using dynamicImport, add the component to the route array as 'component' key. -routes.forEach((route) => { - if (route.component) { - route['component'] = dynamicImport(route.component) - } -}) - const App = () => { return ( @@ -56,6 +54,9 @@ const App = () => { > {routes.map((route, idx) => { const allowedRoles = route.allowedRoles + const Routecomponent = dynamicImport(route.path) + console.log('route', route) + console.log('Routecomponent', Routecomponent) return ( route.component && ( { CIPP - {route.name} - + diff --git a/src/importsMap.jsx b/src/importsMap.jsx new file mode 100644 index 000000000000..fd9f92661d53 --- /dev/null +++ b/src/importsMap.jsx @@ -0,0 +1,138 @@ +import React from 'react' + export const importsMap = { + "/home": React.lazy(() => import('./views/home/Home')), + "/cipp/logs": React.lazy(() => import('./views/cipp/Logs')), + "/cipp/scheduler": React.lazy(() => import('./views/cipp/Scheduler')), + "/cipp/statistics": React.lazy(() => import('./views/cipp/Statistics')), + "/cipp/404": React.lazy(() => import('./views/pages/page404/Page404')), + "/cipp/403": React.lazy(() => import('./views/pages/page403/Page403')), + "/cipp/500": React.lazy(() => import('./views/pages/page500/Page500')), + "/identity/administration/users/add": React.lazy(() => import('./views/identity/administration/AddUser')), + "/identity/administration/users/addbulk": React.lazy(() => import('./views/identity/administration/AddUserBulk')), + "/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/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')), + "/identity/administration/groups/add": React.lazy(() => import('./views/identity/administration/AddGroup')), + "/identity/administration/group-templates": React.lazy(() => import('./views/identity/administration/GroupTemplates')), + "/identity/administration/group-add-template": React.lazy(() => import('./views/identity/administration/AddGroupTemplate')), + "/identity/administration/deploy-group-template": React.lazy(() => import('./views/identity/administration/DeployGroupTemplate')), + "/identity/administration/groups/edit": React.lazy(() => import('./views/identity/administration/EditGroup')), + "/identity/administration/groups/view": React.lazy(() => import('./views/identity/administration/ViewGroup')), + "/identity/administration/groups": React.lazy(() => import('./views/identity/administration/Groups')), + "/identity/administration/roles": React.lazy(() => import('./views/identity/administration/Roles')), + "/identity/administration/deleted-items": React.lazy(() => import('./views/identity/administration/Deleted')), + "/teams-share/teams/business-voice": React.lazy(() => import('./views/teams-share/teams/BusinessVoice')), + "/identity/administration/offboarding-wizard": React.lazy(() => import('./views/identity/administration/OffboardingWizard')), + "/endpoint/reports/devices": React.lazy(() => import('./views/endpoint/intune/Devices')), + "/identity/reports/mfa-report": React.lazy(() => import('./views/identity/reports/MFAReport')), + "/identity/reports/inactive-users-report": React.lazy(() => import('./views/identity/reports/InactiveUsers')), + "/identity/reports/Signin-report": React.lazy(() => import('./views/identity/reports/SignIns')), + "/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/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')), + "/tenant/administration/alertsqueue": React.lazy(() => import('./views/tenant/administration/ListAlertsQueue')), + "/tenant/administration/graph-explorer": React.lazy(() => import('./views/tenant/administration/GraphExplorer')), + "/tenant/administration/service-health": React.lazy(() => import('./views/tenant/administration/ServiceHealth')), + "/tenant/administration/enterprise-apps": React.lazy(() => import('./views/tenant/administration/ListEnterpriseApps')), + "/tenant/administration/app-consent-requests": React.lazy(() => import('./views/tenant/administration/ListAppConsentRequests')), + "/tenant/conditional/list-policies": React.lazy(() => import('./views/tenant/conditional/ConditionalAccess')), + "/tenant/conditional/deploy-vacation": React.lazy(() => import('./views/tenant/conditional/DeployVacation')), + "/tenant/conditional/list-named-locations": React.lazy(() => import('./views/tenant/conditional/NamedLocations')), + "/tenant/conditional/deploy": React.lazy(() => import('./views/tenant/conditional/DeployCA')), + "/tenant/conditional/deploy-named-location": React.lazy(() => import('./views/tenant/conditional/DeployNamedLocation')), + "/tenant/conditional/list-template": React.lazy(() => import('./views/tenant/conditional/ListCATemplates')), + "/tenant/conditional/add-template": React.lazy(() => import('./views/tenant/conditional/AddCATemplate')), + "/tenant/administration/list-licenses": React.lazy(() => import('./views/tenant/administration/ListLicences')), + "/tenant/administration/application-consent": React.lazy(() => import('./views/tenant/administration/ListOauthApps')), + "/tenant/standards/list-applied-standards": React.lazy(() => import('./views/tenant/standards/ListAppliedStandards')), + "/tenant/standards/bpa-report": React.lazy(() => import('./views/tenant/standards/BestPracticeAnalyser')), + "/tenant/standards/domains-analyser": React.lazy(() => import('./views/tenant/standards/DomainsAnalyser')), + "/tenant/standards/individual-domains": React.lazy(() => import('./views/tenant/standards/IndividualDomain')), + "/tenant/administration/tenantlookup": React.lazy(() => import('./views/tenant/administration/TenantLookup')), + "/tenant/tools/geoiplookup": React.lazy(() => import('./views/tenant/administration/GeoIPLookup')), + "/tenant/tools/bpa-report-builder": React.lazy(() => import('./views/tenant/standards/BPAReportBuilder')), + "/tenant/standards/alert-list": React.lazy(() => import('./views/security/incidents/ListAlerts')), + "/endpoint/applications/list": React.lazy(() => import('./views/endpoint/applications/ApplicationsList')), + "/endpoint/applications/queue": React.lazy(() => import('./views/endpoint/applications/ListApplicationQueue')), + "/endpoint/applications/add-choco-app": React.lazy(() => import('./views/endpoint/applications/ApplicationsAddChocoApp')), + "/endpoint/applications/add-winget-app": React.lazy(() => import('./views/endpoint/applications/ApplicationsAddWinGet')), + "/endpoint/applications/add-office-app": React.lazy(() => import('./views/endpoint/applications/ApplicationsAddOffice')), + "/endpoint/applications/add-rmm-app": React.lazy(() => import('./views/endpoint/applications/ApplicationsAddRMM')), + "/endpoint/autopilot/add-device": React.lazy(() => import('./views/endpoint/autopilot/AutopilotAddDevice')), + "/endpoint/autopilot/add-profile": React.lazy(() => import('./views/endpoint/autopilot/AutopilotAddProfile')), + "/endpoint/autopilot/add-status-page": React.lazy(() => import('./views/endpoint/autopilot/AutopilotAddStatusPage')), + "/endpoint/autopilot/list-devices": React.lazy(() => import('./views/endpoint/autopilot/AutopilotListDevices')), + "/endpoint/autopilot/list-profiles": React.lazy(() => import('./views/endpoint/autopilot/AutopilotListProfiles')), + "/endpoint/autopilot/list-status-pages": React.lazy(() => import('./views/endpoint/autopilot/AutopilotListStatusPages')), + "/endpoint/MEM/list-policies": React.lazy(() => import('./views/endpoint/intune/MEMListPolicies')), + "/endpoint/MEM/list-compliance-policies": React.lazy(() => import('./views/endpoint/intune/MEMListCompliance')), + "/endpoint/MEM/list-appprotection-policies": React.lazy(() => import('./views/endpoint/intune/MEMListAppProtection')), + "/endpoint/MEM/edit-policy": React.lazy(() => import('./views/endpoint/intune/MEMEditPolicy')), + "/endpoint/MEM/ca-policies": React.lazy(() => import('./views/endpoint/intune/MEMCAPolicies')), + "/endpoint/MEM/add-policy": React.lazy(() => import('./views/endpoint/intune/MEMAddPolicy')), + "/endpoint/MEM/add-policy-template": React.lazy(() => import('./views/endpoint/intune/MEMAddPolicyTemplate')), + "/endpoint/MEM/list-templates": React.lazy(() => import('./views/endpoint/intune/MEMListPolicyTemplates')), + "/security/defender/deployment": React.lazy(() => import('./views/security/defender/DeployDefender')), + "/security/defender/list-defender": React.lazy(() => import('./views/security/defender/ListDefender')), + "/security/defender/list-defender-tvm": React.lazy(() => import('./views/security/defender/ListVuln')), + "/teams-share/onedrive/list": React.lazy(() => import('./views/teams-share/onedrive/OneDriveList')), + "/teams-share/sharepoint/list-sharepoint": React.lazy(() => import('./views/teams-share/sharepoint/SharepointList')), + "/teams-share/teams/list-team": React.lazy(() => import('./views/teams-share/teams/TeamsListTeam')), + "/teams-share/teams/view-team-settings": React.lazy(() => import('./views/teams-share/teams/ViewTeamSettings')), + "/teams-share/teams/add-team": React.lazy(() => import('./views/teams-share/teams/TeamsAddTeam')), + "/teams-share/teams/teams-activity": React.lazy(() => import('./views/teams-share/teams/TeamsActivity')), + "/email/administration/contacts": React.lazy(() => import('./views/email-exchange/administration/ContactsList')), + "/email/connectors/list-connectors": React.lazy(() => import('./views/email-exchange/connectors/ConnectorList')), + "/email/connectors/deploy-connector": React.lazy(() => import('./views/email-exchange/connectors/DeployConnector')), + "/email/connectors/add-connector-templates": React.lazy(() => import('./views/email-exchange/connectors/AddConnectorTemplate')), + "/email/connectors/list-connector-templates": React.lazy(() => import('./views/email-exchange/connectors/ListConnectorTemplates')), + "/email/transport/list-rules": React.lazy(() => import('./views/email-exchange/transport/TransportRules')), + "/email/transport/deploy-rules": React.lazy(() => import('./views/email-exchange/transport/DeployTransport')), + "/email/transport/list-templates": React.lazy(() => import('./views/email-exchange/transport/ListTransportTemplates')), + "/email/transport/add-template": React.lazy(() => import('./views/email-exchange/transport/AddTransportTemplate')), + "/email/spamfilter/list-spamfilter": React.lazy(() => import('./views/email-exchange/spamfilter/Spamfilter')), + "/email/spamfilter/deploy": React.lazy(() => import('./views/email-exchange/spamfilter/DeploySpamfilter')), + "/email/spamfilter/list-templates": React.lazy(() => import('./views/email-exchange/spamfilter/ListSpamfilterTemplates')), + "/email/tools/mailbox-restore-wizard": React.lazy(() => import('./views/email-exchange/tools/MailboxRestoreWizard')), + "/email/tools/mailbox-restores": React.lazy(() => import('./views/email-exchange/tools/MailboxRestores')), + "/email/tools/mail-test": React.lazy(() => import('./views/email-exchange/tools/MailTest')), + "/email/spamfilter/add-template": React.lazy(() => import('./views/email-exchange/spamfilter/AddSpamfilterTemplate')), + "/email/administration/edit-mailbox-permissions": React.lazy(() => import('./views/email-exchange/administration/EditMailboxPermissions')), + "/email/administration/add-shared-mailbox": React.lazy(() => import('./views/email-exchange/administration/AddSharedMailbox')), + "/email/administration/add-contact": React.lazy(() => import('./views/email-exchange/administration/AddContact')), + "/email/administration/edit-calendar-permissions": React.lazy(() => import('./views/email-exchange/administration/EditCalendarPermissions')), + "/email/administration/view-mobile-devices": React.lazy(() => import('./views/email-exchange/administration/ViewMobileDevices')), + "/email/administration/edit-contact": React.lazy(() => import('./views/email-exchange/administration/EditContact')), + "/email/administration/mailboxes": React.lazy(() => import('./views/email-exchange/administration/MailboxesList')), + "/email/administration/mailbox-rules": React.lazy(() => import('./views/email-exchange/administration/MailboxRuleList')), + "/email/administration/Quarantine": React.lazy(() => import('./views/email-exchange/administration/QuarantineList')), + "/email/reports/mailbox-statistics": React.lazy(() => import('./views/email-exchange/reports/MailboxStatisticsList')), + "/email/reports/SharedMailboxEnabledAccount": React.lazy(() => import('./views/email-exchange/reports/SharedMailboxEnabledAccount')), + "/email/reports/mailbox-cas-settings": React.lazy(() => import('./views/email-exchange/reports/MailboxClientAccessSettingsList')), + "/email/reports/message-trace": React.lazy(() => import('./views/email-exchange/reports/MessageTrace')), + "/cipp/user-settings": React.lazy(() => import('./views/cipp/UserSettings')), + "/email/reports/phishing-policies": React.lazy(() => import('./views/email-exchange/reports/PhishingPoliciesList')), + "/security/incidents/list-alerts": React.lazy(() => import('./views/security/incidents/ListAlerts')), + "/security/incidents/list-incidents": React.lazy(() => import('./views/security/incidents/ListIncidents')), + "/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/setup": React.lazy(() => import('./views/cipp/Setup')), + "/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')), + "/tenant/administration/gdap-roles": React.lazy(() => import('./views/tenant/administration/ListGDAPRoles')), + "/tenant/administration/gdap-relationships": React.lazy(() => import('././views/tenant/administration/ListGDAPRelationships')), + "/tenant/administration/appapproval": React.lazy(() => import('./views/cipp/AppApproval')), + "/tenant/administration/gdap-status": React.lazy(() => import('./views/tenant/administration/ListGDAPQueue')), + "/tenant/standards/list-standards": React.lazy(() => import('./views/tenant/standards/ListStandards')), + "/tenant/administration/tenant-offboarding-wizard": React.lazy(() => import('./views/tenant/administration/TenantOffboardingWizard')), + "/tenant/administration/tenant-onboarding-wizard": React.lazy(() => import('./views/tenant/administration/TenantOnboardingWizard')), +} +export default importsMap \ No newline at end of file From 0bd5e954eb7e09be2f647da7259a86e1c7e246b2 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 3 Apr 2024 22:11:30 -0400 Subject: [PATCH 004/130] Allow retry button while running --- .../administration/TenantOnboardingWizard.jsx | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/views/tenant/administration/TenantOnboardingWizard.jsx b/src/views/tenant/administration/TenantOnboardingWizard.jsx index bdd301663507..a6eb19460e55 100644 --- a/src/views/tenant/administration/TenantOnboardingWizard.jsx +++ b/src/views/tenant/administration/TenantOnboardingWizard.jsx @@ -157,20 +157,19 @@ const RelationshipOnboarding = ({ relationship, gdapRoles, autoMapRoles, addMiss })} {onboardingStatus.isSuccess && ( <> - {onboardingStatus.data?.Status != 'running' && - onboardingStatus.data?.Status != 'queued' && ( - - getOnboardingStatus({ - path: '/api/ExecOnboardTenant?Retry=True', - values: { id: relationship.id, gdapRoles, autoMapRoles, addMissingGroups }, - }) - } - className="mb-3 me-2" - > - Retry - - )} + {onboardingStatus.data?.Status != 'queued' && ( + + getOnboardingStatus({ + path: '/api/ExecOnboardTenant?Retry=True', + values: { id: relationship.id, gdapRoles, autoMapRoles, addMissingGroups }, + }) + } + className="mb-3 me-2" + > + Retry + + )} {onboardingStatus.data?.Logs && ( ) } +RelationshipOnboarding.propTypes = { + relationship: PropTypes.object.isRequired, + gdapRoles: PropTypes.array, + autoMapRoles: PropTypes.bool, + addMissingGroups: PropTypes.bool, +} const TenantOnboardingWizard = () => { const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) From 791e567da878ec3fabb696e7d31f95b329819f74 Mon Sep 17 00:00:00 2001 From: Esco Date: Thu, 4 Apr 2024 13:37:28 +0200 Subject: [PATCH 005/130] DefaultValue Switch and Input --- src/components/forms/RFFComponents.jsx | 9 ++++++++- src/views/tenant/standards/ListAppliedStandards.jsx | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/forms/RFFComponents.jsx b/src/components/forms/RFFComponents.jsx index 089a0c6b5226..3e5f42e30b0b 100644 --- a/src/components/forms/RFFComponents.jsx +++ b/src/components/forms/RFFComponents.jsx @@ -102,9 +102,16 @@ export const RFFCFormSwitch = ({ disabled = false, initialValue, onClick, + defaultValue, }) => { return ( - + {({ meta, input }) => ( { )} {component.type === 'AdminRolesMultiSelect' && ( From 8659d9ee1d4d8f7a104f0ac6af2a83df9579ff32 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 8 Apr 2024 14:38:27 +0200 Subject: [PATCH 006/130] fixes columns not being correct. --- src/components/tables/CippTable.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/tables/CippTable.jsx b/src/components/tables/CippTable.jsx index 3c246cbecb40..f35820d32e2a 100644 --- a/src/components/tables/CippTable.jsx +++ b/src/components/tables/CippTable.jsx @@ -739,7 +739,7 @@ export default function CippTable({ Date: Mon, 8 Apr 2024 14:45:56 +0200 Subject: [PATCH 007/130] set default size to A3, with fit to page enabled for reprinting. --- src/components/buttons/PdfButton.jsx | 2 +- src/components/tables/CippTable.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/buttons/PdfButton.jsx b/src/components/buttons/PdfButton.jsx index 4bfda756a7e4..38c00ce6124f 100644 --- a/src/components/buttons/PdfButton.jsx +++ b/src/components/buttons/PdfButton.jsx @@ -9,7 +9,7 @@ import { useSelector } from 'react-redux' function ExportPDFButton(props) { const base64 = useSelector((state) => state.app.reportImage) - const exportPDF = (pdfData, pdfHeaders, pdfSize = 'A4', reportName = 'report') => { + const exportPDF = (pdfData, pdfHeaders, pdfSize = 'A3', reportName = 'report') => { const unit = 'pt' const size = pdfSize // Use A1, A2, A3 or A4 const orientation = 'landscape' // portrait or landscape diff --git a/src/components/tables/CippTable.jsx b/src/components/tables/CippTable.jsx index f35820d32e2a..510aaa3ec9f0 100644 --- a/src/components/tables/CippTable.jsx +++ b/src/components/tables/CippTable.jsx @@ -740,7 +740,7 @@ export default function CippTable({ key="export-pdf-action-visible" pdfData={filtered} pdfHeaders={updatedColumns} - pdfSize="A4" + pdfSize="A3" reportName={reportName} nameText="Export Visible Columns" /> From 5c939cd649843a6d46cdb9a569e97b4961c7edcf Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 8 Apr 2024 23:33:48 +0200 Subject: [PATCH 008/130] add sort for devices --- src/views/identity/administration/Devices.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/views/identity/administration/Devices.jsx b/src/views/identity/administration/Devices.jsx index b67740e3136d..04d6df19da58 100644 --- a/src/views/identity/administration/Devices.jsx +++ b/src/views/identity/administration/Devices.jsx @@ -159,6 +159,7 @@ const DevicesList = () => { TenantFilter: tenant?.defaultDomainName, Endpoint: 'devices', $format: 'application/json', + sort: 'displayName', }, columns, tableProps: { From 8c765c28d39c071a01deafaf6cce8c9ffb935a96 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Apr 2024 22:08:02 -0400 Subject: [PATCH 009/130] update footer add augmentt remove netfriends --- public/img/augmentt-dark.png | Bin 0 -> 5798 bytes public/img/augmentt-light.png | Bin 0 -> 34107 bytes public/img/netfriends.png | Bin 3779 -> 0 bytes public/img/netfriends_dark.png | Bin 14343 -> 0 bytes src/components/layout/AppFooter.jsx | 14 +++++++------- 5 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 public/img/augmentt-dark.png create mode 100644 public/img/augmentt-light.png delete mode 100644 public/img/netfriends.png delete mode 100644 public/img/netfriends_dark.png diff --git a/public/img/augmentt-dark.png b/public/img/augmentt-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..949d3eebe8d353af8ea0b5625cb0e125c9dc8b21 GIT binary patch literal 5798 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 literal 0 HcmV?d00001 diff --git a/public/img/netfriends.png b/public/img/netfriends.png deleted file mode 100644 index e53cc963113d98292371277bdf6cb4ac6bfab807..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3779 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 xod35r?IC+3znZFT(@< { const isDark = currentTheme === 'impact' || (currentTheme === 'default' && preferredTheme === 'impact') - const netfriends = isDark ? '/img/netfriends_dark.png' : '/img/netfriends.png' const datto = isDark ? '/img/datto.png' : '/img/datto.png' const huntress = isDark ? '/img/huntress_teal.png' : '/img/huntress_teal.png' const rewst = isDark ? '/img/rewst_dark.png' : '/img/rewst.png' const ninjaone = isDark ? '/img/ninjaone_dark.png' : '/img/ninjaone.png' + const augmentt = isDark ? '/img/augmentt-dark.png' : '/img/augmentt-light.png' return (

This application is sponsored by - + - + - + - - + + - +

From f3d2bb9394f9bec7b9e5dec42d607a0e47dfd72f Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Apr 2024 22:08:51 -0400 Subject: [PATCH 010/130] use $orderby and $count for devices --- src/views/identity/administration/Devices.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/views/identity/administration/Devices.jsx b/src/views/identity/administration/Devices.jsx index 04d6df19da58..664f7552757c 100644 --- a/src/views/identity/administration/Devices.jsx +++ b/src/views/identity/administration/Devices.jsx @@ -159,7 +159,8 @@ const DevicesList = () => { TenantFilter: tenant?.defaultDomainName, Endpoint: 'devices', $format: 'application/json', - sort: 'displayName', + $orderby: 'displayName', + $count: true, }, columns, tableProps: { From acf1eeb10aa2acb0562407e5679685f90544b9db Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 9 Apr 2024 13:22:19 +0200 Subject: [PATCH 011/130] removed unused vars --- src/views/email-exchange/administration/ViewMobileDevices.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/views/email-exchange/administration/ViewMobileDevices.jsx b/src/views/email-exchange/administration/ViewMobileDevices.jsx index 78c835647e61..19a58344894a 100644 --- a/src/views/email-exchange/administration/ViewMobileDevices.jsx +++ b/src/views/email-exchange/administration/ViewMobileDevices.jsx @@ -4,9 +4,8 @@ import { CippPageList } from 'src/components/layout' import useQuery from 'src/hooks/useQuery' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { CellTip, cellDateFormatter } from 'src/components/tables' -import { faEye, faEdit, faEllipsisV, faMobileAlt } from '@fortawesome/free-solid-svg-icons' +import { faEllipsisV } from '@fortawesome/free-solid-svg-icons' import { CippActionsOffcanvas } from 'src/components/utilities' -import { Link } from 'react-router-dom' import { CButton } from '@coreui/react' //TODO: Add CellBoolean From 4e5007f185d74fe4365cdbaa001e078b6af66ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 9 Apr 2024 22:35:37 +0200 Subject: [PATCH 012/130] Change to use tenantID for DA --- src/views/tenant/standards/DomainsAnalyser.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/tenant/standards/DomainsAnalyser.jsx b/src/views/tenant/standards/DomainsAnalyser.jsx index 30d830a0ad09..528ba47c4a58 100644 --- a/src/views/tenant/standards/DomainsAnalyser.jsx +++ b/src/views/tenant/standards/DomainsAnalyser.jsx @@ -295,7 +295,7 @@ const DomainsAnalyser = () => { }, ], path: `/api/ListDomainAnalyser`, - params: { tenantFilter: currentTenant.defaultDomainName }, + params: { tenantFilter: currentTenant.customerId }, columns, reportName: 'Domains-Analyzer', tableProps: { From 3b78518028128ae34cd090ee6e048a7dfbe72bf0 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 10 Apr 2024 15:49:09 -0400 Subject: [PATCH 013/130] Update augmentt link --- src/components/layout/AppFooter.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/layout/AppFooter.jsx b/src/components/layout/AppFooter.jsx index 2a736bc8439a..6437061d9133 100644 --- a/src/components/layout/AppFooter.jsx +++ b/src/components/layout/AppFooter.jsx @@ -30,7 +30,11 @@ const AppFooter = () => { - + From 33e8eaf2079441a3a1cad779f61df0e9c7ca08b0 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 10 Apr 2024 21:58:51 +0200 Subject: [PATCH 014/130] json translation prework --- package-lock.json | 14 ++ package.json | 1 + src/_nav.jsx | 5 - src/components/utilities/CippJsonView.jsx | 117 +++++++++++++++++ src/data/translator.json | 152 ++++++++++++++++++++++ src/views/tenant/conditional/DeployCA.jsx | 115 +++++++++++----- 6 files changed, 367 insertions(+), 37 deletions(-) create mode 100644 src/components/utilities/CippJsonView.jsx create mode 100644 src/data/translator.json diff --git a/package-lock.json b/package-lock.json index 2542349cba28..3bf07f84211f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@rjsf/core": "^5.12.1", "@rjsf/utils": "^5.12.1", "@rjsf/validator-ajv8": "^5.12.1", + "@uiw/react-json-view": "^2.0.0-alpha.23", "axios": "^1.6.2", "buffer": "^6.0.3", "chart.js": "^3.5.1", @@ -1907,6 +1908,19 @@ "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" }, + "node_modules/@uiw/react-json-view": { + "version": "2.0.0-alpha.23", + "resolved": "https://registry.npmjs.org/@uiw/react-json-view/-/react-json-view-2.0.0-alpha.23.tgz", + "integrity": "sha512-GT0fy/K7+xSsfhvV4PVx2qPRomr/RjzFnerCjglfTYX0oEjFe9S2UwnhqOBaSHrfaCL6ccALZ2c+qV73eqop9Q==", + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.10.0", + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", diff --git a/package.json b/package.json index 042efe701871..2c5978c8a8c1 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@rjsf/core": "^5.12.1", "@rjsf/utils": "^5.12.1", "@rjsf/validator-ajv8": "^5.12.1", + "@uiw/react-json-view": "^2.0.0-alpha.23", "axios": "^1.6.2", "buffer": "^6.0.3", "chart.js": "^3.5.1", diff --git a/src/_nav.jsx b/src/_nav.jsx index f55c93339bd1..f4ea23a753d9 100644 --- a/src/_nav.jsx +++ b/src/_nav.jsx @@ -254,11 +254,6 @@ const _nav = [ name: 'CA Templates', to: '/tenant/conditional/list-template', }, - { - component: CNavItem, - name: 'Add CA Template', - to: '/tenant/conditional/add-template', - }, { component: CNavItem, name: 'Named Locations', diff --git a/src/components/utilities/CippJsonView.jsx b/src/components/utilities/CippJsonView.jsx new file mode 100644 index 000000000000..635da48a9cdf --- /dev/null +++ b/src/components/utilities/CippJsonView.jsx @@ -0,0 +1,117 @@ +import React from 'react' +import JsonView from '@uiw/react-json-view' +import { useSelector } from 'react-redux' +import { useMediaPredicate } from 'react-media-hook' +import translator from 'src/data/translator.json' +const githubLightTheme = { + '--w-rjv-font-family': 'monospace', + '--w-rjv-color': '#6f42c1', + '--w-rjv-key-string': '#6f42c1', + '--w-rjv-background-color': '#ffffff', + '--w-rjv-line-color': '#ddd', + '--w-rjv-arrow-color': '#6e7781', + '--w-rjv-edit-color': 'var(--w-rjv-color)', + '--w-rjv-info-color': '#0000004d', + '--w-rjv-update-color': '#ebcb8b', + '--w-rjv-copied-color': '#002b36', + '--w-rjv-copied-success-color': '#28a745', + + '--w-rjv-curlybraces-color': '#6a737d', + '--w-rjv-colon-color': '#24292e', + '--w-rjv-brackets-color': '#6a737d', + '--w-rjv-quotes-color': 'var(--w-rjv-key-string)', + '--w-rjv-quotes-string-color': 'var(--w-rjv-type-string-color)', + + '--w-rjv-type-string-color': '#032f62', + '--w-rjv-type-int-color': '#005cc5', + '--w-rjv-type-float-color': '#005cc5', + '--w-rjv-type-bigint-color': '#005cc5', + '--w-rjv-type-boolean-color': '#d73a49', + '--w-rjv-type-date-color': '#005cc5', + '--w-rjv-type-url-color': '#0969da', + '--w-rjv-type-null-color': '#d73a49', + '--w-rjv-type-nan-color': '#859900', + '--w-rjv-type-undefined-color': '#005cc5', +} + +export const githubDarkTheme = { + '--w-rjv-font-family': 'monospace', + '--w-rjv-color': '#79c0ff', + '--w-rjv-key-string': '#79c0ff', + '--w-rjv-background-color': '#0d1117', + '--w-rjv-line-color': '#94949480', + '--w-rjv-arrow-color': '#ccc', + '--w-rjv-edit-color': 'var(--w-rjv-color)', + '--w-rjv-info-color': '#7b7b7b', + '--w-rjv-update-color': '#ebcb8b', + '--w-rjv-copied-color': '#79c0ff', + '--w-rjv-copied-success-color': '#28a745', + + '--w-rjv-curlybraces-color': '#8b949e', + '--w-rjv-colon-color': '#c9d1d9', + '--w-rjv-brackets-color': '#8b949e', + '--w-rjv-quotes-color': 'var(--w-rjv-key-string)', + '--w-rjv-quotes-string-color': 'var(--w-rjv-type-string-color)', + + '--w-rjv-type-string-color': '#a5d6ff', + '--w-rjv-type-int-color': '#79c0ff', + '--w-rjv-type-float-color': '#79c0ff', + '--w-rjv-type-bigint-color': '#79c0ff', + '--w-rjv-type-boolean-color': '#ffab70', + '--w-rjv-type-date-color': '#79c0ff', + '--w-rjv-type-url-color': '#4facff', + '--w-rjv-type-null-color': '#ff7b72', + '--w-rjv-type-nan-color': '#859900', + '--w-rjv-type-undefined-color': '#79c0ff', +} +const matchPattern = (key, patterns) => { + return patterns.some((pattern) => { + if (pattern.includes('*')) { + // Replace * with regex that matches any character sequence and create a RegExp object + const regex = new RegExp(`^${pattern.replace(/\*/g, '.*')}$`, 'i') + return regex.test(key) + } + return pattern.toLowerCase() === key.toLowerCase() + }) +} + +const translateAndRemoveKeys = (obj, removePatterns = []) => { + if (Array.isArray(obj)) { + return obj.map((item) => translateAndRemoveKeys(item, removePatterns)) + } else if (obj !== null && typeof obj === 'object') { + return Object.entries(obj).reduce((acc, [key, value]) => { + // Check if the key matches any removal pattern + if (!matchPattern(key, removePatterns)) { + const translatedKey = + translator[key.toLowerCase()] || + key.replace(/([A-Z])/g, ' $1').replace(/^./, (str) => str.toUpperCase()) + acc[translatedKey] = translateAndRemoveKeys(value, removePatterns) // Recursively process + } + return acc + }, {}) + } + return obj +} + +function CippJsonView({ object, removeKeys = ['*@odata*'] }) { + const currentTheme = useSelector((state) => state.app.currentTheme) + const preferredTheme = useMediaPredicate('(prefers-color-scheme: dark)') ? 'impact' : 'cyberdrain' + const theme = + currentTheme === 'impact' || (currentTheme === preferredTheme) === 'impact' + ? githubDarkTheme + : githubLightTheme + const translatedObject = translateAndRemoveKeys(object, removeKeys) + console.log('translatedObject', translatedObject) + + return ( + + ) +} + +export default CippJsonView diff --git a/src/data/translator.json b/src/data/translator.json new file mode 100644 index 000000000000..6144de6d1a7d --- /dev/null +++ b/src/data/translator.json @@ -0,0 +1,152 @@ +{ + "accessrights": "AccessRights", + "accountenabled": "Enabled", + "acquisitiondate": "Purchased on", + "actions": "Actions", + "activateddatetime": "Activated", + "activationstate": "Activation State", + "activesyncenabled": "ActiveSync Enabled", + "additionalemailaddresses": "Additional Email Addresses", + "affecteddevices": "Affected Devices Names", + "affecteddevicescount": "# Affected Devices", + "allocated": "Allocated (GB)", + "applicationid": "Application ID", + "assignedlicenses": "Licenses", + "assignedto": "Assigned to User", + "autoextendduration": "Auto Extend", + "avgseconds": "Avg (seconds)", + "builtincontrols": "Built-in Controls", + "callcount": "Call Count", + "cippconnectortype": "Type", + "clientapptypes": "Client App Types", + "clienttype": "Client Type", + "clientversion": "Client Version", + "command": "Command", + "comment": "Comment", + "companyname": "Company", + "created": " Created Date (Local)", + "createddatetime": "Created", + "customer/displayname": "Tenant", + "cveid": "CVE ID", + "date": "Date", + "defaultdomainname": "Default Domain", + "desc": "Description", + "deviceaccessstate": "Access State", + "devicefriendlyname": "Friendly Name", + "devicemodel": "Model", + "deviceos": "OS", + "devicetype": "Device Type", + "dkimenabled": "DKIM Enabled", + "dmarcactionpolicy": "DMARC Action Policy", + "dmarcpercentagepass": "DMARC % Pass", + "dmarcpresent": "DMARC Present", + "dnssecpresent": "DNSSec Enabled", + "domain": "Domain", + "ecpenabled": "ECP Enabled", + "enddatetime": "End", + "ewsenabled": "EWS Enabled", + "excludeapplications": "Exclude Applications", + "excluded": "Excluded", + "excludedate": "Exclude Date", + "excludegroups": "Exclude Groups", + "excludelocations": "Exclude Locations", + "excludeplatforms": "Platform Exc", + "excludeuser": "Exclude User", + "excludeusers": "Exclude Users", + "executedtime": "Last executed time", + "execution": "Execute", + "executioncount": "Executions", + "exploitabilitylevel": "Exploit Publicly Available", + "filecount": "File Count (Total)", + "firstsync": "First Sync", + "forwardto": "Forwards To", + "grantcontrolsoperator": "Control Operator", + "guid": "GUID", + "hasarchive": "Archiving Enabled", + "identity": "Identity", + "if": "If", + "imapenabled": "IMAP Enabled", + "includeapplications": "Include Applications", + "includegroups": "Include Groups", + "includelocations": "Include Locations", + "includeplatforms": "Platform Inc", + "includeunknowncountriesandregions": "Include Unknown Countries", + "includeusers": "Include Users", + "isocountrycode": "Country", + "isoperatorconnect": "Operator Connect", + "istransportrulescoped": "Only apply via transport rules", + "itemcount": "Item Count (Total)", + "lastactive": "Last Active", + "lastsuccesssync": "Last Succesfull Sync", + "lastsyncattempt": "Last Sync Attempt", + "mail": "Email", + "mailboxownerid": "Mailbox", + "mailnickname": "Mail nickname", + "mailprovider": "Mail Provider", + "mapienabled": "MAPI Enabled", + "maxseconds": "Max (seconds)", + "meetingcount": "Meeting Count", + "modifieddatetime": "Last Modified", + "mxpasstest": "MX Pass Test", + "numbertype": "Number Type", + "objectid": "Object ID", + "onpremisessyncenabled": "AD Synced", + "osplatform": "Platform", + "owaenabled": "OWA Enabled", + "parameters": "Parameters", + "placename": "Location", + "policyname": "Blocked by Policy", + "popenabled": "POP Enabled", + "postexecution": "Sending to", + "primarysmtpaddress": "Primary E-mail", + "product_display_name": "Display Name", + "quickscanoverdue": "CVSS Score", + "quotagb": "Quota (GB)", + "quotaused": "Quota Used(%)", + "rangeorlocation": "Locations or IPs", + "receivedtime": "Received on", + "recipientaddress": "Recipient", + "recipienttype": "Mailbox Type", + "recipienttypedetails": "Recipient Type Details", + "recurrence": "Recurrence", + "requestdate": "Request Date", + "requestreason": "Reason", + "requeststatus": "Status", + "requestuser": "Requester", + "scheduledtime": "Scheduled Time", + "scope": "Scope (Permissions)", + "scorepercentage": "Security Score", + "securityupdateavailable": "Update Available", + "senderaddress": "Sender", + "senderipaddresses": "Sender IP Addresses", + "service": "Service", + "smarthost": "Smarthost", + "softwarename": "Application Name", + "softwarevendor": "Vendor", + "spfpassall": "SPF Pass Test", + "startat": "Migration Started at", + "starttime": "Permissions Granted (Local)", + "state": "State", + "storageusedinbytes": "Used Space (GB)", + "subject": "Subject", + "tags": "Tags", + "taskstate": "Task State", + "teamschat": "Chat Count", + "telephonenumber": "Phone Number", + "template": "Root Template", + "tenantid": "Tenant ID", + "tenantname": "Tenant Name", + "tlsdomain": "TLS Domain", + "tlssendercertificatename": "Inbound Connector Hostname", + "tlssettings": "TLS Settings", + "totalseconds": "Total (seconds)", + "upn": "UPN", + "url": "URL", + "usedgb": "Used (GB)", + "user": "User", + "userprincipalname": "User Prinicipal Name", + "usertype": "User Type", + "visibility": "Visibility", + "vulnerabilityseveritylevel": "Severity", + "locationinfo": "Location Info" +} diff --git a/src/views/tenant/conditional/DeployCA.jsx b/src/views/tenant/conditional/DeployCA.jsx index f03618ba0bce..34c75c675fb5 100644 --- a/src/views/tenant/conditional/DeployCA.jsx +++ b/src/views/tenant/conditional/DeployCA.jsx @@ -6,9 +6,10 @@ import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons' import { CippWizard } from 'src/components/layout' import { WizardTableField } from 'src/components/tables' import PropTypes from 'prop-types' -import { RFFCFormRadio, RFFCFormSelect, RFFCFormTextarea } from 'src/components/forms' +import { Condition, RFFCFormRadio, RFFCFormSelect, RFFCFormTextarea } from 'src/components/forms' import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' import { OnChange } from 'react-final-form-listeners' +import CippJsonView from 'src/components/utilities/CippJsonView' const Error = ({ name }) => ( { label: template.displayName, }))} placeholder="Select a template" - label="Please choose a template to apply, or enter the information manually." + label="Please choose a template to apply." /> )} + + {/* always hide the textarea */} + + + + + + - + + + {/* eslint-disable react/prop-types */} + {(props) => { + const json = props.values?.rawjson ? JSON.parse(props.values.rawjson) : {} + return ( + <> + + + ) + }} + - - - - + + + + + + + + + + + + + + + + + +
From 5889082481037dd3080b4d50c3f8f4ea66ea9237 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 10 Apr 2024 18:06:56 -0400 Subject: [PATCH 015/130] Partner webhooks --- src/views/cipp/app-settings/CIPPSettings.jsx | 24 ++- .../cipp/app-settings/SettingsPartner.jsx | 152 ++++++++++++++++++ 2 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 src/views/cipp/app-settings/SettingsPartner.jsx diff --git a/src/views/cipp/app-settings/CIPPSettings.jsx b/src/views/cipp/app-settings/CIPPSettings.jsx index ff43ad2a5a94..c1e6b9463fd7 100644 --- a/src/views/cipp/app-settings/CIPPSettings.jsx +++ b/src/views/cipp/app-settings/CIPPSettings.jsx @@ -11,6 +11,7 @@ import { SettingsLicenses } from 'src/views/cipp/app-settings/SettingsLicenses.j import { SettingsExtensions } from 'src/views/cipp/app-settings/SettingsExtensions.jsx' import { SettingsMaintenance } from 'src/views/cipp/app-settings/SettingsMaintenance.jsx' import { SettingsExtensionMappings } from 'src/views/cipp/app-settings/SettingsExtensionMappings.jsx' +import { SettingsPartner } from 'src/views/cipp/app-settings/SettingsPartner.jsx' /** * This function returns the settings page content for CIPP. @@ -35,15 +36,18 @@ export default function CIPPSettings() { Notifications setActive(5)} href="#"> - Licenses + Partner Webhooks setActive(6)} href="#"> - Maintenance + Licenses setActive(7)} href="#"> - Extensions + Maintenance setActive(8)} href="#"> + Extensions + + setActive(9)} href="#"> Extension Mappings @@ -68,22 +72,26 @@ export default function CIPPSettings() { - - - + + - + - + + + + + + diff --git a/src/views/cipp/app-settings/SettingsPartner.jsx b/src/views/cipp/app-settings/SettingsPartner.jsx new file mode 100644 index 000000000000..e56b479cc7a2 --- /dev/null +++ b/src/views/cipp/app-settings/SettingsPartner.jsx @@ -0,0 +1,152 @@ +import { useGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app.js' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' +import { + CBadge, + CButton, + CCallout, + CCard, + CCardBody, + CCardHeader, + CCardTitle, + CCol, + CForm, + CRow, + CSpinner, +} from '@coreui/react' +import { Form, useForm } from 'react-final-form' +import { RFFCFormInput, RFFCFormSwitch, RFFSelectSearch } from 'src/components/forms/index.js' +import React, { useEffect, useState } from 'react' +import { CippCallout } from 'src/components/layout/index.js' +import { CippCodeBlock } from 'src/components/utilities' +import { CellDate } from 'src/components/tables' + +/** + * Sets the notification settings. + * @returns {JSX.Element} The notification settings component. + */ +export function SettingsPartner() { + const webhookConfig = useGenericGetRequestQuery({ + path: '/api/ExecPartnerWebhook', + params: { Action: 'ListSubscription' }, + }) + const webhookEvents = useGenericGetRequestQuery({ + path: '/api/ExecPartnerWebhook', + params: { Action: 'ListEventTypes' }, + }) + const [submitWebhook, webhookCreateResult] = useLazyGenericPostRequestQuery() + + const onSubmit = (values) => { + const shippedValues = { + EventType: values?.EventType?.map((event) => event.value), + } + submitWebhook({ + path: '/api/ExecPartnerWebhook?Action=CreateSubscription', + values: shippedValues, + }).then((res) => { + webhookConfig.refetch() + }) + } + + return ( + + + + <> + webhookConfig.refetch()} + className="mb-2" + disabled={webhookConfig.isFetching} + > + {webhookConfig.isFetching ? ( + <> + Loading... + + ) : ( + <> + + Refresh + + )} + + + {!webhookConfig.isFetching && webhookConfig.error && ( + Error loading data + )} + {webhookConfig.isSuccess && ( + <> +

Webhook Configuration

+ + +

Webhook URL

+ +
+ +

Last Updated

+ +
+ +

Subscribed Events

+
({ + label: event, + value: event, + })), + }} + render={({ handleSubmit }) => ( + <> + + ({ + name: event, + value: event, + }))} + multi={true} + refreshFunction={() => webhookEvents.refetch()} + helpText="Select the events you want to receive notifications for." + /> + + {webhookCreateResult.isFetching ? ( + <> + + Saving... + + ) : ( + 'Save' + )} + + + + )} + /> + {webhookCreateResult.isSuccess && ( + + {webhookCreateResult?.data?.Results} + + )} + + + + )} + + + + ) +} From 9d0cb5b12ba5e7c71ee9f76d190bdd54833dd210 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 11 Apr 2024 00:24:25 +0200 Subject: [PATCH 016/130] removed console logs --- .../utilities/CippActionsOffcanvas.jsx | 2 +- src/components/utilities/CippJsonView.jsx | 117 ++++++++++++++---- .../utilities/CippListOffcanvas.jsx | 2 +- src/components/utilities/CippOffcanvas.jsx | 2 +- src/hooks/useNavFavouriteCheck.jsx | 2 +- src/hooks/useRouteNavCompare.jsx | 2 +- src/views/cipp/UserSettings.jsx | 14 +-- .../applications/ApplicationsAddChocoApp.jsx | 2 +- .../applications/ApplicationsAddWinGet.jsx | 4 +- .../endpoint/intune/MEMListAppProtection.jsx | 2 +- .../administration/ListGDAPRelationships.jsx | 1 - src/views/tenant/administration/Tenants.jsx | 4 +- 12 files changed, 112 insertions(+), 42 deletions(-) diff --git a/src/components/utilities/CippActionsOffcanvas.jsx b/src/components/utilities/CippActionsOffcanvas.jsx index 6bc9d896d71c..c2cab6d24539 100644 --- a/src/components/utilities/CippActionsOffcanvas.jsx +++ b/src/components/utilities/CippActionsOffcanvas.jsx @@ -40,7 +40,7 @@ export default function CippActionsOffcanvas(props) { (modalMessage, modalUrl, modalType = 'GET', modalBody, modalInput, modalDropdown) => { const handlePostConfirm = () => { const selectedValue = inputRef.current.value - console.log(inputRef) + //console.log(inputRef) let additionalFields = {} if (inputRef.current.nodeName === 'SELECT') { diff --git a/src/components/utilities/CippJsonView.jsx b/src/components/utilities/CippJsonView.jsx index 635da48a9cdf..90b7f9fe1fe2 100644 --- a/src/components/utilities/CippJsonView.jsx +++ b/src/components/utilities/CippJsonView.jsx @@ -1,8 +1,23 @@ -import React from 'react' -import JsonView from '@uiw/react-json-view' +import React, { useState } from 'react' import { useSelector } from 'react-redux' import { useMediaPredicate } from 'react-media-hook' -import translator from 'src/data/translator.json' +import JsonView from '@uiw/react-json-view' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { + CAccordion, + CAccordionBody, + CAccordionHeader, + CAccordionItem, + CCard, + CCardBody, + CCardHeader, + CCardTitle, + CCol, + CFormSwitch, + CRow, +} from '@coreui/react' +import translator from 'src/data/translator.json' // Ensure the path to your translator.json is correct + const githubLightTheme = { '--w-rjv-font-family': 'monospace', '--w-rjv-color': '#6f42c1', @@ -64,10 +79,10 @@ export const githubDarkTheme = { '--w-rjv-type-nan-color': '#859900', '--w-rjv-type-undefined-color': '#79c0ff', } + const matchPattern = (key, patterns) => { return patterns.some((pattern) => { if (pattern.includes('*')) { - // Replace * with regex that matches any character sequence and create a RegExp object const regex = new RegExp(`^${pattern.replace(/\*/g, '.*')}$`, 'i') return regex.test(key) } @@ -75,42 +90,100 @@ const matchPattern = (key, patterns) => { }) } +const removeNullOrEmpty = (obj) => { + if (Array.isArray(obj)) { + const filteredArray = obj.filter((item) => item != null).map(removeNullOrEmpty) + return filteredArray.length > 0 ? filteredArray : null + } else if (typeof obj === 'object' && obj !== null) { + const result = Object.entries(obj).reduce((acc, [key, value]) => { + const processedValue = removeNullOrEmpty(value) + if (processedValue != null) { + acc[key] = processedValue + } + return acc + }, {}) + return Object.keys(result).length > 0 ? result : null + } + return obj +} + const translateAndRemoveKeys = (obj, removePatterns = []) => { + obj = removeNullOrEmpty(obj) if (Array.isArray(obj)) { return obj.map((item) => translateAndRemoveKeys(item, removePatterns)) - } else if (obj !== null && typeof obj === 'object') { + } else if (typeof obj === 'object' && obj !== null) { return Object.entries(obj).reduce((acc, [key, value]) => { - // Check if the key matches any removal pattern if (!matchPattern(key, removePatterns)) { const translatedKey = translator[key.toLowerCase()] || key.replace(/([A-Z])/g, ' $1').replace(/^./, (str) => str.toUpperCase()) - acc[translatedKey] = translateAndRemoveKeys(value, removePatterns) // Recursively process + acc[translatedKey] = translateAndRemoveKeys(value, removePatterns) } return acc }, {}) } return obj } +function renderObjectAsColumns(object, level = 0, maxLevel = 4) { + const content = [] + let nextLevelObject = null + + for (const [key, value] of Object.entries(object)) { + if (level < maxLevel && typeof value === 'object' && value !== null && !Array.isArray(value)) { + nextLevelObject = value // Prepare the next level's object for rendering + continue // Skip to avoid rendering this as a separate card + } + + // Render current level key-value pairs + content.push( + + + {key} + {JSON.stringify(value, null, 2)} + + , + ) + } + + return ( + <> + {content} + {nextLevelObject && renderObjectAsColumns(nextLevelObject, level + 1, maxLevel)} + + ) +} -function CippJsonView({ object, removeKeys = ['*@odata*'] }) { - const currentTheme = useSelector((state) => state.app.currentTheme) - const preferredTheme = useMediaPredicate('(prefers-color-scheme: dark)') ? 'impact' : 'cyberdrain' +function CippJsonView({ + jsonData = { 'No Data Selected': 'No Data Selected' }, + removeKeys = ['*@odata*', 'id', 'guid', 'createdDateTime', '*modified*', 'deletedDateTime'], +}) { + const [showRawJson, setShowRawJson] = useState(false) const theme = - currentTheme === 'impact' || (currentTheme === preferredTheme) === 'impact' - ? githubDarkTheme - : githubLightTheme - const translatedObject = translateAndRemoveKeys(object, removeKeys) - console.log('translatedObject', translatedObject) + useSelector((state) => state.app.currentTheme) === 'dark' ? githubDarkTheme : githubLightTheme + const cleanedJsonData = translateAndRemoveKeys(jsonData, removeKeys) return ( - +
+ + + Settings + + setShowRawJson(!showRawJson)} /> + {showRawJson ? ( + + ) : ( + {renderObjectAsColumns(cleanedJsonData)} + )} + + + +
) } diff --git a/src/components/utilities/CippListOffcanvas.jsx b/src/components/utilities/CippListOffcanvas.jsx index b27c1ed3dbd4..4fbde6931ce3 100644 --- a/src/components/utilities/CippListOffcanvas.jsx +++ b/src/components/utilities/CippListOffcanvas.jsx @@ -39,7 +39,7 @@ CippListOffcanvas.propTypes = { } export function OffcanvasListSection({ title, items }) { - console.log(items) + //console.log(items) const mappedItems = items.map((item, key) => ({ value: item.content, label: item.heading })) return ( <> diff --git a/src/components/utilities/CippOffcanvas.jsx b/src/components/utilities/CippOffcanvas.jsx index d3eaece82e94..eb20726fbfbe 100644 --- a/src/components/utilities/CippOffcanvas.jsx +++ b/src/components/utilities/CippOffcanvas.jsx @@ -23,7 +23,7 @@ export default function CippOffcanvas(props) { color="link" size="lg" onClick={() => { - console.log('refresh') + //console.log('refresh') props.refreshFunction() }} > diff --git a/src/hooks/useNavFavouriteCheck.jsx b/src/hooks/useNavFavouriteCheck.jsx index 90f710405b18..2045b101ea25 100644 --- a/src/hooks/useNavFavouriteCheck.jsx +++ b/src/hooks/useNavFavouriteCheck.jsx @@ -15,7 +15,7 @@ export const useNavFavouriteCheck = (navigation) => { to: '/favorites', icon: , items: favourites.map((item) => { - console.log(item) + //console.log(item) return { name: item.value.name, to: item.value.to, diff --git a/src/hooks/useRouteNavCompare.jsx b/src/hooks/useRouteNavCompare.jsx index aee23a37474d..27fc1f9a3a14 100644 --- a/src/hooks/useRouteNavCompare.jsx +++ b/src/hooks/useRouteNavCompare.jsx @@ -21,7 +21,7 @@ export const useRouteNavCompare = (navigation) => { ) { return true } else { - console.log('Removing route', item) + //console.log('Removing route', item) return false } }) diff --git a/src/views/cipp/UserSettings.jsx b/src/views/cipp/UserSettings.jsx index 21ab0182287c..955101c02921 100644 --- a/src/views/cipp/UserSettings.jsx +++ b/src/views/cipp/UserSettings.jsx @@ -216,14 +216,12 @@ const UserSettings = () => { multi={true} values={_nav .reduce((acc, val) => acc.concat(val.items), []) - .map( - (item) => ( - console.log(item), - { - name: item?.name, - value: { to: item?.to, name: item?.name }, - } - ), + .map((item) => + // console.log(item), + ({ + name: item?.name, + value: { to: item?.to, name: item?.name }, + }), )} allowCreate={false} refreshFunction={() => diff --git a/src/views/endpoint/applications/ApplicationsAddChocoApp.jsx b/src/views/endpoint/applications/ApplicationsAddChocoApp.jsx index 7d4ef791b391..2995fd32bb16 100644 --- a/src/views/endpoint/applications/ApplicationsAddChocoApp.jsx +++ b/src/views/endpoint/applications/ApplicationsAddChocoApp.jsx @@ -87,7 +87,7 @@ const ApplyStandard = () => { {(value) => { let template = foundPackages.data.Results.filter(function (obj) { - console.log(value) + //console.log(value) return obj.packagename === value }) onChange(template[0][set]) diff --git a/src/views/endpoint/applications/ApplicationsAddWinGet.jsx b/src/views/endpoint/applications/ApplicationsAddWinGet.jsx index dda13eecf7c6..525c39d93799 100644 --- a/src/views/endpoint/applications/ApplicationsAddWinGet.jsx +++ b/src/views/endpoint/applications/ApplicationsAddWinGet.jsx @@ -88,10 +88,10 @@ const AddWinGet = () => { {(value) => { let template = foundPackages.data.filter(function (obj) { - console.log(value) + // console.log(value) return obj.packagename === value }) - console.log(template[0]) + //console.log(template[0]) onChange(template[0][set]) }} diff --git a/src/views/endpoint/intune/MEMListAppProtection.jsx b/src/views/endpoint/intune/MEMListAppProtection.jsx index 9f63b4820fde..b3e59de4c9e7 100644 --- a/src/views/endpoint/intune/MEMListAppProtection.jsx +++ b/src/views/endpoint/intune/MEMListAppProtection.jsx @@ -19,7 +19,7 @@ import { cellBooleanFormatter, cellDateFormatter } from 'src/components/tables' const Actions = (row, rowIndex, formatExtraData) => { const [ocVisible, setOCVisible] = useState(false) - console.log(row) + //console.log(row) const tenant = useSelector((state) => state.app.currentTenant) return ( <> diff --git a/src/views/tenant/administration/ListGDAPRelationships.jsx b/src/views/tenant/administration/ListGDAPRelationships.jsx index e55def41f1ce..c463797c16c9 100644 --- a/src/views/tenant/administration/ListGDAPRelationships.jsx +++ b/src/views/tenant/administration/ListGDAPRelationships.jsx @@ -128,7 +128,6 @@ const Actions = (row, rowIndex, formatExtraData) => { ) } - const GDAPRelationships = () => { const columns = [ { diff --git a/src/views/tenant/administration/Tenants.jsx b/src/views/tenant/administration/Tenants.jsx index 87ab0a9d12da..f4d14335e6cc 100644 --- a/src/views/tenant/administration/Tenants.jsx +++ b/src/views/tenant/administration/Tenants.jsx @@ -11,9 +11,9 @@ import { CippTenantOffcanvasRow } from 'src/components/utilities/CippTenantOffca const TenantsList = () => { const TenantListSelector = useSelector((state) => state.app.TenantListSelector) const tenant = useSelector((state) => state.app.currentTenant) - console.log('TenantListSelector', TenantListSelector) + //console.log('TenantListSelector', TenantListSelector) const [columnOmits, setOmitVisible] = useState(TenantListSelector) - console.log('columnOmits', columnOmits) + //console.log('columnOmits', columnOmits) const generatePortalColumn = (portal) => ({ name: portal.label, omit: columnOmits, From 6ce20e0f1697b55cc82fea5008cdfa13f3ec174f Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 11 Apr 2024 00:42:43 +0200 Subject: [PATCH 017/130] testing new layout --- src/components/utilities/CippJsonView.jsx | 96 ++++++++++++----------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/src/components/utilities/CippJsonView.jsx b/src/components/utilities/CippJsonView.jsx index 90b7f9fe1fe2..87a9d94b221a 100644 --- a/src/components/utilities/CippJsonView.jsx +++ b/src/components/utilities/CippJsonView.jsx @@ -1,8 +1,8 @@ import React, { useState } from 'react' +import JsonView from '@uiw/react-json-view' import { useSelector } from 'react-redux' import { useMediaPredicate } from 'react-media-hook' -import JsonView from '@uiw/react-json-view' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import translator from 'src/data/translator.json' import { CAccordion, CAccordionBody, @@ -14,9 +14,14 @@ import { CCardTitle, CCol, CFormSwitch, + CListGroup, + CListGroupItem, + CNav, + CNavItem, + CNavLink, CRow, } from '@coreui/react' -import translator from 'src/data/translator.json' // Ensure the path to your translator.json is correct +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' const githubLightTheme = { '--w-rjv-font-family': 'monospace', @@ -79,10 +84,10 @@ export const githubDarkTheme = { '--w-rjv-type-nan-color': '#859900', '--w-rjv-type-undefined-color': '#79c0ff', } - const matchPattern = (key, patterns) => { return patterns.some((pattern) => { if (pattern.includes('*')) { + // Replace * with regex that matches any character sequence and create a RegExp object const regex = new RegExp(`^${pattern.replace(/\*/g, '.*')}$`, 'i') return regex.test(key) } @@ -92,93 +97,90 @@ const matchPattern = (key, patterns) => { const removeNullOrEmpty = (obj) => { if (Array.isArray(obj)) { + // Filter out null or undefined items and apply recursively const filteredArray = obj.filter((item) => item != null).map(removeNullOrEmpty) + // Additionally, remove empty arrays return filteredArray.length > 0 ? filteredArray : null } else if (typeof obj === 'object' && obj !== null) { const result = Object.entries(obj).reduce((acc, [key, value]) => { const processedValue = removeNullOrEmpty(value) if (processedValue != null) { + // Checks for both null and undefined acc[key] = processedValue } return acc }, {}) + // Additionally, remove empty objects return Object.keys(result).length > 0 ? result : null } return obj } const translateAndRemoveKeys = (obj, removePatterns = []) => { - obj = removeNullOrEmpty(obj) + obj = removeNullOrEmpty(obj) // Clean the object first if (Array.isArray(obj)) { return obj.map((item) => translateAndRemoveKeys(item, removePatterns)) } else if (typeof obj === 'object' && obj !== null) { return Object.entries(obj).reduce((acc, [key, value]) => { + // Check if the key matches any removal pattern if (!matchPattern(key, removePatterns)) { const translatedKey = translator[key.toLowerCase()] || key.replace(/([A-Z])/g, ' $1').replace(/^./, (str) => str.toUpperCase()) - acc[translatedKey] = translateAndRemoveKeys(value, removePatterns) + acc[translatedKey] = translateAndRemoveKeys(value, removePatterns) // Recursively process } return acc }, {}) } return obj } -function renderObjectAsColumns(object, level = 0, maxLevel = 4) { - const content = [] - let nextLevelObject = null - - for (const [key, value] of Object.entries(object)) { - if (level < maxLevel && typeof value === 'object' && value !== null && !Array.isArray(value)) { - nextLevelObject = value // Prepare the next level's object for rendering - continue // Skip to avoid rendering this as a separate card - } - - // Render current level key-value pairs - content.push( - - - {key} - {JSON.stringify(value, null, 2)} - - , - ) - } - - return ( - <> - {content} - {nextLevelObject && renderObjectAsColumns(nextLevelObject, level + 1, maxLevel)} - - ) -} - function CippJsonView({ - jsonData = { 'No Data Selected': 'No Data Selected' }, - removeKeys = ['*@odata*', 'id', 'guid', 'createdDateTime', '*modified*', 'deletedDateTime'], + object = { 'No Data Selected': 'No Data Selected' }, + removeKeys = ['*@odata*', 'created*', '*modified*', 'id', 'guid'], }) { - const [showRawJson, setShowRawJson] = useState(false) + const currentTheme = useSelector((state) => state.app.currentTheme) + const preferredTheme = useMediaPredicate('(prefers-color-scheme: dark)') ? 'impact' : 'cyberdrain' const theme = - useSelector((state) => state.app.currentTheme) === 'dark' ? githubDarkTheme : githubLightTheme - const cleanedJsonData = translateAndRemoveKeys(jsonData, removeKeys) - + currentTheme === 'impact' || (currentTheme === preferredTheme) === 'impact' + ? githubDarkTheme + : githubLightTheme + const translatedObject = translateAndRemoveKeys(object, removeKeys) + const [switchRef, setSwitchRef] = useState(false) + console.log(translatedObject) return (
- - Settings + + + {object.displayName ? `${object.displayName} Settings` : 'Settings'} + - setShowRawJson(!showRawJson)} /> - {showRawJson ? ( + setSwitchRef(!switchRef)} /> + {switchRef ? ( ) : ( - {renderObjectAsColumns(cleanedJsonData)} + + {translatedObject && + Object.keys(translatedObject).map((key) => ( + + + + {key} + + + +
{JSON.stringify(translatedObject[key], null, 2)}
+
+
+
+ ))} +
)}
From ebcc929cd9de1b629a66b44b83c15a6fca399b3d Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 10 Apr 2024 19:58:29 -0400 Subject: [PATCH 018/130] Add webhook testing --- .../cipp/app-settings/SettingsPartner.jsx | 131 ++++++++++++++++-- 1 file changed, 123 insertions(+), 8 deletions(-) diff --git a/src/views/cipp/app-settings/SettingsPartner.jsx b/src/views/cipp/app-settings/SettingsPartner.jsx index e56b479cc7a2..91f45d98c11e 100644 --- a/src/views/cipp/app-settings/SettingsPartner.jsx +++ b/src/views/cipp/app-settings/SettingsPartner.jsx @@ -1,6 +1,9 @@ -import { useGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app.js' +import { + useGenericGetRequestQuery, + useLazyGenericGetRequestQuery, + useLazyGenericPostRequestQuery, +} from 'src/store/api/app.js' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' import { CBadge, CButton, @@ -11,12 +14,13 @@ import { CCardTitle, CCol, CForm, + CLink, CRow, CSpinner, } from '@coreui/react' -import { Form, useForm } from 'react-final-form' -import { RFFCFormInput, RFFCFormSwitch, RFFSelectSearch } from 'src/components/forms/index.js' -import React, { useEffect, useState } from 'react' +import { Form } from 'react-final-form' +import { RFFSelectSearch } from 'src/components/forms/index.js' +import React, { useEffect } from 'react' import { CippCallout } from 'src/components/layout/index.js' import { CippCodeBlock } from 'src/components/utilities' import { CellDate } from 'src/components/tables' @@ -35,6 +39,8 @@ export function SettingsPartner() { params: { Action: 'ListEventTypes' }, }) const [submitWebhook, webhookCreateResult] = useLazyGenericPostRequestQuery() + const [sendTest, sendTestResult] = useLazyGenericGetRequestQuery() + const [checkTest, checkTestResult] = useLazyGenericGetRequestQuery() const onSubmit = (values) => { const shippedValues = { @@ -48,6 +54,25 @@ export function SettingsPartner() { }) } + useEffect(() => { + if ( + sendTestResult.isSuccess && + sendTestResult?.data?.Results?.correlationId && + !checkTestResult?.data?.Results?.results + ) { + setTimeout( + checkTest({ + path: '/api/ExecPartnerWebhook', + params: { + Action: 'ValidateTest', + CorrelationId: sendTestResult?.data?.Results?.correlationId, + }, + }), + 1000, + ) + } + }, [sendTestResult, checkTest, checkTestResult]) + return ( @@ -77,11 +102,25 @@ export function SettingsPartner() { {webhookConfig.isSuccess && ( <>

Webhook Configuration

+ + + Subscribe to Microsoft Partner center webhooks to enable automatic tenant + onboarding and alerting. Updating the settings will replace any existing webhook + subscription with one pointing to CIPP. Refer to the{' '} + + Microsoft Partner Center documentation + {' '} + for more information on the webhook types. + +

Webhook URL

@@ -93,7 +132,7 @@ export function SettingsPartner() { format="short" />
- +

Subscribed Events

{webhookCreateResult.isFetching ? ( @@ -143,6 +182,82 @@ export function SettingsPartner() { )}
+

Webhook Test

+ + + + sendTest({ + path: '/api/ExecPartnerWebhook', + params: { Action: 'SendTest' }, + }) + } + > + {sendTestResult.isFetching ? ( + <> + + Running Test... + + ) : ( + 'Start Test' + )} + + {checkTestResult.isFetching && !checkTestResult?.data?.Results?.result && ( + <> + Waiting for results + + )} + + + + {checkTestResult.isSuccess && ( + <> + +

Status

+ {checkTestResult?.data?.Results.status} +
+ {Array.isArray(checkTestResult?.data?.Results?.results) && ( + <> + +

Status Code

+ + {checkTestResult?.data?.Results?.results[0].responseCode} +
+ {checkTestResult?.data?.Results?.results[0].responseMessage !== '' && ( + +

Response Message

+ {checkTestResult.data.Results.results[0].responseMessage} +
+ )} + +

Date/Time

+ +
+ + )} + + )} +
)} From a7a172261636ee771604a196f216771e980a6602 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 11 Apr 2024 12:13:29 +0200 Subject: [PATCH 019/130] fixes new json view --- src/components/utilities/CippJsonView.jsx | 75 ++++++++++++++++++----- src/views/tenant/conditional/DeployCA.jsx | 8 ++- 2 files changed, 66 insertions(+), 17 deletions(-) diff --git a/src/components/utilities/CippJsonView.jsx b/src/components/utilities/CippJsonView.jsx index 87a9d94b221a..13110e931011 100644 --- a/src/components/utilities/CippJsonView.jsx +++ b/src/components/utilities/CippJsonView.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import JsonView from '@uiw/react-json-view' import { useSelector } from 'react-redux' import { useMediaPredicate } from 'react-media-hook' @@ -8,6 +8,7 @@ import { CAccordionBody, CAccordionHeader, CAccordionItem, + CButton, CCard, CCardBody, CCardHeader, @@ -144,13 +145,60 @@ function CippJsonView({ currentTheme === 'impact' || (currentTheme === preferredTheme) === 'impact' ? githubDarkTheme : githubLightTheme - const translatedObject = translateAndRemoveKeys(object, removeKeys) + const [translatedObject, setTranslatedObject] = useState(() => + translateAndRemoveKeys(object, removeKeys), + ) const [switchRef, setSwitchRef] = useState(false) - console.log(translatedObject) + // Adjusting the expanded state to track selections for up to 4 levels + const [expansionPath, setExpansionPath] = useState([{ object: translatedObject }]) + console.log(object) + + useEffect(() => { + const newTranslatedObject = translateAndRemoveKeys(object, removeKeys) + setTranslatedObject(newTranslatedObject) + setExpansionPath([{ object: newTranslatedObject }]) // Reset the expansion path with the new object + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(object), JSON.stringify(removeKeys)]) + + // Updated to handle deeper level expansions + const handleExpand = (level, content) => { + const newPath = expansionPath.slice(0, level) + newPath.push({ object: content }) + setExpansionPath(newPath) + } + const renderContent = (content, level = 0) => { + if (Array.isArray(content)) { + return ( +
    + {content.map((item, index) => ( +
  • {JSON.stringify(item, null, 2)}
  • + ))} +
+ ) + } else if (typeof content === 'object') { + return Object.entries(content).map(([key, value]) => ( +
+ {key}: + {typeof value === 'object' ? ( + handleExpand(level + 1, value)} variant="ghost"> + + Show More + + ) : ( + ` ${value}` + )} +
+ )) + } else if (typeof content === 'string') { + return content.replace(/(^"|"$)/g, '') // Remove quotes + } + return String(content) + } + return (
- + {object.displayName ? `${object.displayName} Settings` : 'Settings'} @@ -166,20 +214,19 @@ function CippJsonView({ /> ) : ( - {translatedObject && - Object.keys(translatedObject).map((key) => ( - - + {expansionPath.map((expansion, index) => ( + + {Object.entries(expansion.object).map(([key, value]) => ( + {key} - + - -
{JSON.stringify(translatedObject[key], null, 2)}
-
+ {renderContent(value, index)}
-
- ))} + ))} +
+ ))}
)} diff --git a/src/views/tenant/conditional/DeployCA.jsx b/src/views/tenant/conditional/DeployCA.jsx index 34c75c675fb5..cf30e97a342a 100644 --- a/src/views/tenant/conditional/DeployCA.jsx +++ b/src/views/tenant/conditional/DeployCA.jsx @@ -161,7 +161,7 @@ const AddPolicy = () => { {/* eslint-disable react/prop-types */} {(props) => { - const json = props.values?.rawjson ? JSON.parse(props.values.rawjson) : {} + const json = props.values?.rawjson ? JSON.parse(props.values.rawjson) : undefined return ( <> @@ -238,6 +238,8 @@ const AddPolicy = () => { {/* eslint-disable react/prop-types */} {(props) => { + const json = props.values?.rawjson ? JSON.parse(props.values.rawjson) : undefined + return ( <> @@ -251,8 +253,8 @@ const AddPolicy = () => { ))} -
Rule Settings
- {props.values.rawjson} +
Rule Settings
+
From 490710abfe2c3805c705878a850c4eb862af14af Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 11 Apr 2024 12:20:59 +0200 Subject: [PATCH 020/130] add displayName sort if available. --- src/components/utilities/CippJsonView.jsx | 25 +++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/components/utilities/CippJsonView.jsx b/src/components/utilities/CippJsonView.jsx index 13110e931011..4a738c33a600 100644 --- a/src/components/utilities/CippJsonView.jsx +++ b/src/components/utilities/CippJsonView.jsx @@ -151,10 +151,31 @@ function CippJsonView({ const [switchRef, setSwitchRef] = useState(false) // Adjusting the expanded state to track selections for up to 4 levels const [expansionPath, setExpansionPath] = useState([{ object: translatedObject }]) - console.log(object) useEffect(() => { - const newTranslatedObject = translateAndRemoveKeys(object, removeKeys) + const sortObject = (obj) => { + const order = ['displayName', 'name', 'state'] // Define the desired order + const sortedKeys = Object.keys(obj).sort((a, b) => { + const indexA = order.indexOf(a) + const indexB = order.indexOf(b) + if (indexA === -1 && indexB === -1) { + return 0 // If both keys are not in the order array, maintain the original order + } else if (indexA === -1) { + return 1 // If only key A is not in the order array, move key B to a higher position + } else if (indexB === -1) { + return -1 // If only key B is not in the order array, move key A to a higher position + } else { + return indexA - indexB // Sort based on the index in the order array + } + }) + const sortedObject = {} + sortedKeys.forEach((key) => { + sortedObject[key] = obj[key] + }) + return sortedObject + } + + const newTranslatedObject = sortObject(translateAndRemoveKeys(sortObject(object), removeKeys)) setTranslatedObject(newTranslatedObject) setExpansionPath([{ object: newTranslatedObject }]) // Reset the expansion path with the new object // eslint-disable-next-line react-hooks/exhaustive-deps From db636c17b9c8b4e3316462fa7c88642ddd1f0850 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 11 Apr 2024 12:51:34 +0200 Subject: [PATCH 021/130] replaces json overview --- src/views/endpoint/intune/MEMAddPolicy.jsx | 54 +++++++++++----------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/src/views/endpoint/intune/MEMAddPolicy.jsx b/src/views/endpoint/intune/MEMAddPolicy.jsx index 9ab14f4840ca..161b0134665f 100644 --- a/src/views/endpoint/intune/MEMAddPolicy.jsx +++ b/src/views/endpoint/intune/MEMAddPolicy.jsx @@ -15,6 +15,7 @@ import { } from 'src/components/forms' import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' import { OnChange } from 'react-final-form-listeners' +import CippJsonView from 'src/components/utilities/CippJsonView' const Error = ({ name }) => ( {

Step 2

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

@@ -155,25 +150,27 @@ const AddPolicy = () => { label: template.Displayname, }))} placeholder="Select a template" - label="Please choose a template to apply, or enter the information manually." + label="Please choose a template to apply." /> )} - + + + @@ -198,18 +195,23 @@ const AddPolicy = () => { - + + + {(props) => { + console.log(props.values.RAWJson) + const json = props.values?.RAWJson ? JSON.parse(props.values.RAWJson) : undefined return ( <> + {props.values.RAWJson?.match('%.*%') && From d973469eab372a49a338f7a468f7f7ce931fd8f0 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 11 Apr 2024 13:34:18 +0200 Subject: [PATCH 022/130] prettification --- src/views/tenant/conditional/DeployCA.jsx | 37 ++++++++++++++++++----- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/views/tenant/conditional/DeployCA.jsx b/src/views/tenant/conditional/DeployCA.jsx index cf30e97a342a..46de880a02da 100644 --- a/src/views/tenant/conditional/DeployCA.jsx +++ b/src/views/tenant/conditional/DeployCA.jsx @@ -6,7 +6,13 @@ import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons' import { CippWizard } from 'src/components/layout' import { WizardTableField } from 'src/components/tables' import PropTypes from 'prop-types' -import { Condition, RFFCFormRadio, RFFCFormSelect, RFFCFormTextarea } from 'src/components/forms' +import { + Condition, + RFFCFormRadio, + RFFCFormSelect, + RFFCFormSwitch, + RFFCFormTextarea, +} from 'src/components/forms' import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' import { OnChange } from 'react-final-form-listeners' import CippJsonView from 'src/components/utilities/CippJsonView' @@ -174,26 +180,37 @@ const AddPolicy = () => { + + + + + Warning: This will remove all exclusions and apply to all users. You might be + locked out of your tenant if you have not not excluded any roles. Please make sure + this is the action you want to perform. + + + @@ -224,7 +241,11 @@ const AddPolicy = () => { > - + + + + +
From f8b3f37085f0c28f75663174db10a539c3019295 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 11 Apr 2024 15:26:51 +0200 Subject: [PATCH 023/130] null safe edit user --- src/views/identity/administration/EditUser.jsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/views/identity/administration/EditUser.jsx b/src/views/identity/administration/EditUser.jsx index 4f2aef34773e..3cfc21641997 100644 --- a/src/views/identity/administration/EditUser.jsx +++ b/src/views/identity/administration/EditUser.jsx @@ -137,10 +137,12 @@ const EditUser = () => { }, license: precheckedLicenses, //if currentSettings.defaultAttributes exists. Set each of the keys inside of currentSettings.defaultAttributes.label to the value of the user attribute found in the user object. - defaultAttributes: currentSettings?.userSettingsDefaults?.defaultAttributes.reduce( - (o, key) => Object.assign(o, { [key.label]: { Value: user[key.label] } }), - {}, - ), + defaultAttributes: currentSettings?.userSettingsDefaults?.defaultAttributes + ? currentSettings?.userSettingsDefaults?.defaultAttributes.reduce( + (o, key) => Object.assign(o, { [key.label]: { Value: user[key.label] } }), + {}, + ) + : [], } const formDisabled = queryError === true || !!userError || !user || Object.keys(user).length === 0 @@ -410,7 +412,7 @@ const EditUser = () => { <> - {currentSettings?.userSettingsDefaults?.defaultAttributes.map( + {currentSettings?.userSettingsDefaults?.defaultAttributes?.map( (attribute, idx) => ( From e4e581b7b39cf700d8abe372e9a52acdad2d9d69 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 11 Apr 2024 15:28:00 +0200 Subject: [PATCH 024/130] null safe adduser --- src/views/identity/administration/AddUser.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/identity/administration/AddUser.jsx b/src/views/identity/administration/AddUser.jsx index f35d42385b9d..96186c358ed9 100644 --- a/src/views/identity/administration/AddUser.jsx +++ b/src/views/identity/administration/AddUser.jsx @@ -330,7 +330,7 @@ const AddUser = () => { <> - {currentSettings?.userSettingsDefaults?.defaultAttributes.map( + {currentSettings?.userSettingsDefaults?.defaultAttributes?.map( (attribute, idx) => ( From 4a03aac31599c67623c7bd09ba31fde52f566b55 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 11 Apr 2024 09:59:30 -0400 Subject: [PATCH 025/130] update augmentt logo --- public/img/augmentt-dark.png | Bin 5798 -> 1566 bytes public/img/augmentt-light.png | Bin 34107 -> 3682 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/public/img/augmentt-dark.png b/public/img/augmentt-dark.png index 949d3eebe8d353af8ea0b5625cb0e125c9dc8b21..c10f55697569f7a50bafc85eaa835663f71d9411 100644 GIT binary patch delta 1562 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 Zif7mVcgaf=dZ#l6Fw!^EtI@$d{4XbOl~Diy literal 34107 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 From 5651726ab8972feffe5ed4cf4bf20091185620d4 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 11 Apr 2024 18:29:03 +0200 Subject: [PATCH 026/130] added option to create templates for standards. --- .../tenant/standards/ListAppliedStandards.jsx | 181 +++++++++++++----- 1 file changed, 128 insertions(+), 53 deletions(-) diff --git a/src/views/tenant/standards/ListAppliedStandards.jsx b/src/views/tenant/standards/ListAppliedStandards.jsx index f6647dac2bc2..ccbde72fa071 100644 --- a/src/views/tenant/standards/ListAppliedStandards.jsx +++ b/src/views/tenant/standards/ListAppliedStandards.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useRef, useState } from 'react' import { CButton, CCallout, @@ -12,8 +12,9 @@ import { CAccordionItem, CWidgetStatsB, CBadge, + CFormInput, } from '@coreui/react' -import { Form } from 'react-final-form' +import { Form, FormSpy } from 'react-final-form' import { Condition, RFFCFormInput, @@ -37,54 +38,12 @@ import { CippTable, cellBooleanFormatter } from 'src/components/tables' import allStandardsList from 'src/data/standards' import CippCodeOffCanvas from 'src/components/utilities/CippCodeOffcanvas' import GDAPRoles from 'src/data/GDAPRoles' +import Select from 'react-select' -const RefreshAction = () => { - const [execStandards, execStandardsResults] = useLazyGenericGetRequestQuery() - const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) - const showModal = (selectedTenant) => - ModalService.confirm({ - body: ( -
- Are you sure you want to run the standards now?
- Please note: this runs every three hours automatically. -
- ), - onConfirm: () => - execStandards({ path: `api/ExecStandardsRun?Tenantfilter=${selectedTenant}` }), - }) - - return ( - <> - {execStandardsResults.data?.Results === - 'Already running. Please wait for the current instance to finish' && ( -
{execStandardsResults.data?.Results}
- )} -

- showModal('AllTenants')} size="sm" className="m-1"> - {execStandardsResults.isLoading && } - {execStandardsResults.error && ( - - )} - {execStandardsResults.isSuccess && } - Run Standards Now (All Tenants) - - showModal(tenantDomain)} size="sm" className="m-1"> - {execStandardsResults.isLoading && } - {execStandardsResults.error && ( - - )} - {execStandardsResults.isSuccess && } - Run Standards Now (Selected Tenant) - -

- - ) -} const DeleteAction = () => { const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) const [execStandards, execStandardsResults] = useLazyGenericGetRequestQuery() - const showModal = () => ModalService.confirm({ body:
Are you sure you want to delete this standard?
, @@ -109,8 +68,107 @@ const DeleteAction = () => { ) } const ApplyNewStandard = () => { + const [templateStandard, setTemplateStandard] = useState() + console.log(templateStandard) + const RefreshAction = () => { + const [execStandards, execStandardsResults] = useLazyGenericGetRequestQuery() + const { + data: listStandardTemplates = [], + isFetching, + isSuccess, + isError, + } = useGenericGetRequestQuery({ + path: 'api/listStandardTemplates', + }) + const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) + const showModal = (selectedTenant) => + ModalService.confirm({ + body: ( +
+ Are you sure you want to run the standards now?
+ Please note: this runs every three hours automatically. +
+ ), + onConfirm: () => + execStandards({ path: `api/ExecStandardsRun?Tenantfilter=${selectedTenant}` }), + }) + const ourRef = useRef() + const TemplateModal = () => + ModalService.open({ + body: ( +
+ {isFetching && } + {isError && 'Something went wrong loading your templates'} + {isSuccess && ( + 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'} + + )} + ) } From 38565da22d337aeb57c7c7a7fe59de325b0e5372 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 22 Apr 2024 22:54:23 +0200 Subject: [PATCH 071/130] prettification --- .../cipp/app-settings/SettingsBackend.jsx | 383 ++++++++---------- .../cipp/app-settings/SettingsSuperAdmin.jsx | 164 ++++---- 2 files changed, 239 insertions(+), 308 deletions(-) diff --git a/src/views/cipp/app-settings/SettingsBackend.jsx b/src/views/cipp/app-settings/SettingsBackend.jsx index 58e3bc2b594b..c57fa366997c 100644 --- a/src/views/cipp/app-settings/SettingsBackend.jsx +++ b/src/views/cipp/app-settings/SettingsBackend.jsx @@ -11,241 +11,180 @@ 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 - -
-
-
- - - - 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', - '_blank', - 'toolbar=no,scrollbars=yes,resizable=yes,menubar=no,location=no,status=no', - ) - } - rel="noreferrer" - > - Cloud Shell - - setVisible(true)} className="mb-3"> - Command Reference - -
-
+ + {BackendCardList.map((card, index) => ( + + + {card.description} + - - setVisible(false)} - title="Command Reference" - > -
Function App Config
- -
Function App Deployment
- -
Watch Function Logs
- -
Static Web App Config
- -
List CIPP Users
- -
- -
+ ))} + + + + Cloud Shell + + +

Launch an Azure Cloud Shell Window

+ + window.open( + 'https://shell.azure.com/powershell', + '_blank', + 'toolbar=no,scrollbars=yes,resizable=yes,menubar=no,location=no,status=no', + ) + } + rel="noreferrer" + > + Cloud Shell + + setVisible(true)} className="mb-3"> + Command Reference + +
+
+
+ + 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/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} + + )} +
+
-
-
+ + ) } From 6897990f0b23aea324d0d0fd70630de5cc2001c8 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 22 Apr 2024 23:08:50 +0200 Subject: [PATCH 072/130] prettification --- .../app-settings/SettingsNotifications.jsx | 290 +++++++++--------- 1 file changed, 148 insertions(+), 142 deletions(-) diff --git a/src/views/cipp/app-settings/SettingsNotifications.jsx b/src/views/cipp/app-settings/SettingsNotifications.jsx index 6a0b1b73450a..cf66765d185e 100644 --- a/src/views/cipp/app-settings/SettingsNotifications.jsx +++ b/src/views/cipp/app-settings/SettingsNotifications.jsx @@ -19,6 +19,7 @@ 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. @@ -33,150 +34,155 @@ export function SettingsNotifications() { } 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 - + - - ) - }} - /> - - - )} + + + + + + + + + + + + + + + + + + + + + ) + }} + /> + )} + ) } From ef1c6c7a50547ba2c77b5cdb1abbe683c946e15e Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 22 Apr 2024 23:11:58 +0200 Subject: [PATCH 073/130] notification prettification --- src/views/cipp/app-settings/SettingsNotifications.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/cipp/app-settings/SettingsNotifications.jsx b/src/views/cipp/app-settings/SettingsNotifications.jsx index cf66765d185e..c22d47a8afff 100644 --- a/src/views/cipp/app-settings/SettingsNotifications.jsx +++ b/src/views/cipp/app-settings/SettingsNotifications.jsx @@ -33,7 +33,7 @@ export function SettingsNotifications() { configNotifications(values) } return ( - <> + )} - + ) } From f4db1400f20552b1b8c0139cd410729ab9d6822b Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 22 Apr 2024 23:48:08 +0200 Subject: [PATCH 074/130] upgrade extension stuff --- src/data/Extensions.json | 2 +- .../cipp/app-settings/SettingsExtensions.jsx | 183 ++++++++---------- .../app-settings/SettingsNotifications.jsx | 12 +- 3 files changed, 87 insertions(+), 110 deletions(-) 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/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/SettingsNotifications.jsx b/src/views/cipp/app-settings/SettingsNotifications.jsx index c22d47a8afff..4dc512373b5a 100644 --- a/src/views/cipp/app-settings/SettingsNotifications.jsx +++ b/src/views/cipp/app-settings/SettingsNotifications.jsx @@ -4,17 +4,7 @@ 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' From 4fb8a49cec0c11f7b774ada377e2553ecee8601b Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 22 Apr 2024 23:57:07 +0200 Subject: [PATCH 075/130] prettification --- src/views/cipp/app-settings/SettingsBackend.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/views/cipp/app-settings/SettingsBackend.jsx b/src/views/cipp/app-settings/SettingsBackend.jsx index c57fa366997c..7b86ae472901 100644 --- a/src/views/cipp/app-settings/SettingsBackend.jsx +++ b/src/views/cipp/app-settings/SettingsBackend.jsx @@ -79,6 +79,7 @@ export function SettingsBackend() { From 4cd931256243e6377a699dbf669198b4d23bc3a7 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 22 Apr 2024 23:59:50 +0200 Subject: [PATCH 076/130] prettification --- .../cipp/app-settings/SettingsBackend.jsx | 51 ++++++++++--------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/src/views/cipp/app-settings/SettingsBackend.jsx b/src/views/cipp/app-settings/SettingsBackend.jsx index 7b86ae472901..3dc9a9548af5 100644 --- a/src/views/cipp/app-settings/SettingsBackend.jsx +++ b/src/views/cipp/app-settings/SettingsBackend.jsx @@ -88,29 +88,34 @@ export function SettingsBackend() { ))} - - - Cloud Shell - - -

Launch an Azure Cloud Shell Window

- - window.open( - 'https://shell.azure.com/powershell', - '_blank', - 'toolbar=no,scrollbars=yes,resizable=yes,menubar=no,location=no,status=no', - ) - } - rel="noreferrer" - > - Cloud Shell - - setVisible(true)} className="mb-3"> - Command Reference - -
-
+ + {' '} + + window.open( + 'https://shell.azure.com/powershell', + '_blank', + 'toolbar=no,scrollbars=yes,resizable=yes,menubar=no,location=no,status=no', + ) + } + rel="noreferrer" + > + Cloud Shell + + setVisible(true)} className="me-2"> + Command Reference + + + } + > +

Launch an Azure Cloud Shell Window

+
Date: Tue, 23 Apr 2024 01:00:51 +0200 Subject: [PATCH 077/130] add securescore to menu --- src/_nav.jsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/_nav.jsx b/src/_nav.jsx index fbf1ff50f013..26f7e0764890 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', From 2910da61148fe5c3a5c5b740f5b6f558cdf6a1a3 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 23 Apr 2024 12:00:03 +0200 Subject: [PATCH 078/130] test in dev --- src/components/forms/RFFComponents.jsx | 10 +- .../SettingsExtensionMappings.jsx | 176 ++++++++++++------ 2 files changed, 129 insertions(+), 57 deletions(-) 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/views/cipp/app-settings/SettingsExtensionMappings.jsx b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx index 5386afbf1fbd..18586d6a1e79 100644 --- a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx +++ b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx @@ -9,6 +9,7 @@ import { CCardTitle, CCol, CForm, + CRow, CSpinner, } from '@coreui/react' import { Form } from 'react-final-form' @@ -17,6 +18,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' import React from 'react' import { CippCallout } from 'src/components/layout/index.js' +import CippButtonCard from 'src/components/contentcards/CippButtonCard' /** * Retrieves and sets the extension mappings for HaloPSA and NinjaOne. @@ -65,6 +67,59 @@ export function SettingsExtensionMappings() { values: { mappings: values }, }) } + + const [addedAttributes, setAddedAttribute] = React.useState(1) + const [mappingArray, setMappingArray] = React.useState({ HaloPSA: [], NinjaOrgs: [] }) + + const MappingRow = ({ integrationType, index, tenantData, clientData, addButton = true }) => ( + + + ({ + name: tenant.displayName, + value: tenant.customerId, + }))} + //set the name of the other field, to the value of this field by using mappingArray, each time we add a new row, we add a new object to the array. + onChange={(e) => { + console.log(mappingArray[integrationType][index]) + mappingArray[integrationType][index] = { tenant: e.value } + setMappingArray({ ...mappingArray }) + //also complete the normal onChange event + }} + /> + + + + + + ({ + name: client.name, + value: client.value, + }))} + placeholder="Select a HaloPSA Client" + /> + + {addButton && ( + setAddedAttribute(addedAttributes + 1)} + className={`my-4 circular-button`} + title={'+'} + > + + + )} + + ) + return (
{listBackendHaloResult.isUninitialized && @@ -74,61 +129,72 @@ export function SettingsExtensionMappings() { {listBackendNinjaFieldsResult.isUninitialized && listNinjaFieldsBackend({ path: 'api/ExecExtensionMapping?List=NinjaFields' })} <> - - - HaloPSA Mapping Table - - - {listBackendHaloResult.isFetching ? ( - - ) : ( - { - return ( - - - Use the table below to map your client to the correct PSA client - {listBackendHaloResult.isSuccess && - listBackendHaloResult.data.Tenants?.map((tenant) => ( - - ))} - - - - {extensionHaloConfigResult.isFetching && ( - - )} - Set Mappings - - {(extensionHaloConfigResult.isSuccess || - extensionHaloConfigResult.isError) && - !extensionHaloConfigResult.isFetching && ( - - {extensionHaloConfigResult.isSuccess - ? extensionHaloConfigResult.data.Results - : 'Error'} - - )} - - - ) - }} - /> - )} - - + + {extensionHaloConfigResult.isFetching && ( + + )} + Set Mappings + + } + > + {listBackendHaloResult.isFetching ? ( + + ) : ( + { + return ( + + + Use the table below to map your client to the correct PSA client + { + //load all the existing mappings and show them first. + listBackendHaloResult.isSuccess && + listBackendHaloResult.data.HaloClients.map((HaloClient, idx) => + MappingRow({ + integrationType: 'HaloPSA', + index: idx, + clientData: listBackendHaloResult.data.HaloClients, + tenantData: listBackendHaloResult.data.Tenants, + addButton: false, + }), + ) + } + {[...Array(addedAttributes)].map((currentItem, idx) => + MappingRow({ + integrationType: 'HaloPSA', + index: 10000 + idx, //we add 10000 to the index to not conflict with the existing mappings + clientData: listBackendHaloResult.data.HaloClients, + tenantData: listBackendHaloResult.data.Tenants, + }), + )} + + + {(extensionHaloConfigResult.isSuccess || extensionHaloConfigResult.isError) && + !extensionHaloConfigResult.isFetching && ( + + {extensionHaloConfigResult.isSuccess + ? extensionHaloConfigResult.data.Results + : 'Error'} + + )} + + + ) + }} + /> + )} + NinjaOne Field Mapping Table From fa407a3f3064ebae791551b60aa701608ced18ec Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 23 Apr 2024 13:52:40 +0200 Subject: [PATCH 079/130] testing with new layout --- .../SettingsExtensionMappings.jsx | 358 ++++++++++-------- 1 file changed, 196 insertions(+), 162 deletions(-) diff --git a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx index 18586d6a1e79..a1181521ba25 100644 --- a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx +++ b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx @@ -1,17 +1,5 @@ import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app.js' -import { - CButton, - CCallout, - CCard, - CCardBody, - CCardHeader, - CCardText, - CCardTitle, - CCol, - CForm, - CRow, - 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' @@ -19,6 +7,8 @@ import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' import React 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. @@ -69,59 +59,55 @@ export function SettingsExtensionMappings() { } const [addedAttributes, setAddedAttribute] = React.useState(1) - const [mappingArray, setMappingArray] = React.useState({ HaloPSA: [], NinjaOrgs: [] }) + const [mappingArray, setMappingArray] = React.useState('defaultMapping') - const MappingRow = ({ integrationType, index, tenantData, clientData, addButton = true }) => ( - - - ({ - name: tenant.displayName, - value: tenant.customerId, - }))} - //set the name of the other field, to the value of this field by using mappingArray, each time we add a new row, we add a new object to the array. - onChange={(e) => { - console.log(mappingArray[integrationType][index]) - mappingArray[integrationType][index] = { tenant: e.value } - setMappingArray({ ...mappingArray }) - //also complete the normal onChange event - }} - /> - - - - - - ({ - name: client.name, - value: client.value, - }))} - placeholder="Select a HaloPSA Client" - /> - - {addButton && ( - setAddedAttribute(addedAttributes + 1)} - className={`my-4 circular-button`} - title={'+'} - > - - - )} - - ) + const Offcanvas = (row, rowIndex, formatExtraData) => { + return ( + <> + + console.log('Remove Mapping')} + > + + + + + ) + } + const columns = [ + { + name: 'Tenant', + selector: (row) => row['Tenant'], + sortable: true, + cell: (row) => CellTip(row['Tenant']), + exportSelector: 'Tenant', + }, + { + 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', + }, + ] return ( -
+ {listBackendHaloResult.isUninitialized && listHaloBackend({ path: 'api/ExecExtensionMapping?List=Halo' })} {listBackendNinjaOrgsResult.isUninitialized && @@ -129,77 +115,126 @@ export function SettingsExtensionMappings() { {listBackendNinjaFieldsResult.isUninitialized && listNinjaFieldsBackend({ path: 'api/ExecExtensionMapping?List=NinjaFields' })} <> - - {extensionHaloConfigResult.isFetching && ( - - )} - Set Mappings - - } - > - {listBackendHaloResult.isFetching ? ( - - ) : ( - { - return ( - - - Use the table below to map your client to the correct PSA client - { - //load all the existing mappings and show them first. - listBackendHaloResult.isSuccess && - listBackendHaloResult.data.HaloClients.map((HaloClient, idx) => - MappingRow({ - integrationType: 'HaloPSA', - index: idx, - clientData: listBackendHaloResult.data.HaloClients, - tenantData: listBackendHaloResult.data.Tenants, - addButton: false, - }), + + + {extensionHaloConfigResult.isFetching && ( + + )} + Save Mappings + + } + > + {listBackendHaloResult.isFetching ? ( + + ) : ( + { + 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 && ( + ({ + Tenant: key, + haloName: listBackendHaloResult.data?.Mappings[key].label, + haloId: listBackendHaloResult.data?.Mappings[key].value, + }), + )} + isModal={true} + /> ) - } - {[...Array(addedAttributes)].map((currentItem, idx) => - MappingRow({ - integrationType: 'HaloPSA', - index: 10000 + idx, //we add 10000 to the index to not conflict with the existing mappings - clientData: listBackendHaloResult.data.HaloClients, - tenantData: listBackendHaloResult.data.Tenants, - }), - )} - - - {(extensionHaloConfigResult.isSuccess || extensionHaloConfigResult.isError) && - !extensionHaloConfigResult.isFetching && ( - + + ({ + name: tenant.displayName, + value: tenant.customerId, + }))} + //set the name of the other field, to the value of this field by using mappingArray, each time we add a new row, we add a new object to the array. + onChange={(e) => { + setMappingArray(e.value) + }} + /> + + + + + + ({ + name: client.name, + value: client.value, + }))} + placeholder="Select a HaloPSA Client" + /> + + setAddedAttribute(addedAttributes + 1)} + className={`my-4 circular-button`} + title={'+'} > - {extensionHaloConfigResult.isSuccess - ? extensionHaloConfigResult.data.Results - : 'Error'} - - )} - - - ) - }} - /> - )} - - - - NinjaOne Field Mapping Table - - + + + + + + {(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. + + + ) + }} + /> + )} + + + + + {extensionNinjaFieldsConfigResult.isFetching && ( + + )} + Set Mappings + + } + > {listBackendNinjaFieldsResult.isFetching ? ( ) : ( @@ -208,7 +243,7 @@ export function SettingsExtensionMappings() { initialValues={listBackendNinjaFieldsResult.data?.Mappings} render={({ handleSubmit, submitting, values }) => { return ( - +
Organization Global Custom Field Mapping

@@ -249,12 +284,6 @@ export function SettingsExtensionMappings() { ))} - - {extensionNinjaFieldsConfigResult.isFetching && ( - - )} - Set Mappings - {(extensionNinjaFieldsConfigResult.isSuccess || extensionNinjaFieldsConfigResult.isError) && !extensionNinjaFieldsConfigResult.isFetching && ( @@ -276,13 +305,30 @@ export function SettingsExtensionMappings() { }} /> )} - - - - - NinjaOne Organization Mapping Table - - + + + + + + {extensionNinjaOrgsConfigResult.isFetching && ( + + )} + Set Mappings + + onNinjaOrgsAutomap()} className="me-2"> + {extensionNinjaOrgsAutomapResult.isFetching && ( + + )} + Automap NinjaOne Organizations + + + } + > {listBackendNinjaOrgsResult.isFetching ? ( ) : ( @@ -291,7 +337,7 @@ export function SettingsExtensionMappings() { initialValues={listBackendNinjaOrgsResult.data?.Mappings} render={({ handleSubmit, submitting, values }) => { return ( - + Use the table below to map your client to the correct NinjaOne Organization {listBackendNinjaOrgsResult.isSuccess && @@ -306,18 +352,6 @@ export function SettingsExtensionMappings() { ))} - - {extensionNinjaOrgsConfigResult.isFetching && ( - - )} - Set Mappings - - onNinjaOrgsAutomap()} className="me-2"> - {extensionNinjaOrgsAutomapResult.isFetching && ( - - )} - Automap NinjaOne Organizations - {(extensionNinjaOrgsConfigResult.isSuccess || extensionNinjaOrgsConfigResult.isError) && !extensionNinjaFieldsConfigResult.isFetching && ( @@ -349,9 +383,9 @@ export function SettingsExtensionMappings() { }} /> )} - - + + -

+ ) } From e5116eb39664c791057ac519dd62eee14efa3a23 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 23 Apr 2024 15:33:00 +0200 Subject: [PATCH 080/130] new mapping experience. --- .../SettingsExtensionMappings.jsx | 69 ++++++++++++++----- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx index a1181521ba25..5e3f6267829d 100644 --- a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx +++ b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx @@ -4,7 +4,7 @@ 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' @@ -16,6 +16,10 @@ import { CellTip } from 'src/components/tables/CellGenericFormat' * @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 [listHaloBackend, listBackendHaloResult = []] = useLazyGenericGetRequestQuery() const [listNinjaOrgsBackend, listBackendNinjaOrgsResult] = useLazyGenericGetRequestQuery() const [listNinjaFieldsBackend, listBackendNinjaFieldsResult] = useLazyGenericGetRequestQuery() @@ -27,10 +31,15 @@ export function SettingsExtensionMappings() { const [setNinjaFieldsExtensionconfig, extensionNinjaFieldsConfigResult] = useLazyGenericPostRequestQuery() - const onHaloSubmit = (values) => { + const onHaloSubmit = () => { + console.log(haloMappingsArray) + 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) => { @@ -58,8 +67,17 @@ export function SettingsExtensionMappings() { }) } - const [addedAttributes, setAddedAttribute] = React.useState(1) - const [mappingArray, setMappingArray] = React.useState('defaultMapping') + 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]) const Offcanvas = (row, rowIndex, formatExtraData) => { return ( @@ -69,7 +87,11 @@ export function SettingsExtensionMappings() { size="sm" variant="ghost" color="danger" - onClick={() => console.log('Remove Mapping')} + onClick={() => + setHaloMappingsArray((currentHaloMappings) => + currentHaloMappings.filter((item) => item !== row), + ) + } > @@ -80,11 +102,18 @@ export function SettingsExtensionMappings() { const columns = [ { name: 'Tenant', - selector: (row) => row['Tenant'], + selector: (row) => row.Tenant?.displayName, sortable: true, - cell: (row) => CellTip(row['Tenant']), + 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'], @@ -147,13 +176,7 @@ export function SettingsExtensionMappings() { showFilter={true} reportName="none" columns={columns} - data={Object.keys(listBackendHaloResult.data?.Mappings).map( - (key) => ({ - Tenant: key, - haloName: listBackendHaloResult.data?.Mappings[key].label, - haloId: listBackendHaloResult.data?.Mappings[key].value, - }), - )} + data={haloMappingsArray} isModal={true} /> ) @@ -167,7 +190,6 @@ export function SettingsExtensionMappings() { name: tenant.displayName, value: tenant.customerId, }))} - //set the name of the other field, to the value of this field by using mappingArray, each time we add a new row, we add a new object to the array. onChange={(e) => { setMappingArray(e.value) }} @@ -183,11 +205,24 @@ export function SettingsExtensionMappings() { name: client.name, value: client.value, }))} + onChange={(e) => setMappingValue(e)} placeholder="Select a HaloPSA Client" /> setAddedAttribute(addedAttributes + 1)} + onClick={() => + //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={'+'} > From 623007891a318285e611bca83b09438f757949cd Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 23 Apr 2024 10:19:09 -0400 Subject: [PATCH 081/130] filter available relationships for onboarding --- .../tenant/administration/TenantOnboardingWizard.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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', From 6605f34a18b07a95812d91353e815a1d3fb30a19 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 23 Apr 2024 16:31:29 +0200 Subject: [PATCH 082/130] text change --- src/views/cipp/app-settings/SettingsExtensionMappings.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx index 5e3f6267829d..537c46500487 100644 --- a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx +++ b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx @@ -246,8 +246,9 @@ export function SettingsExtensionMappings() { )} + After editing the mappings you must click Save Mappings for the changes to - take effect. + take effect. The table will be saved exactly as presented. ) From b8fd9bf5433007fab95e8c804bac5f27fb3b1e45 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 23 Apr 2024 18:05:07 +0200 Subject: [PATCH 083/130] switches orders --- .../SettingsExtensionMappings.jsx | 157 +++++++++--------- 1 file changed, 79 insertions(+), 78 deletions(-) diff --git a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx index 537c46500487..e29d6bd5198e 100644 --- a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx +++ b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx @@ -257,6 +257,85 @@ export function SettingsExtensionMappings() { )} + + {' '} + + + {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 + {listBackendNinjaOrgsResult.isSuccess && + listBackendNinjaOrgsResult.data.Tenants.map((tenant) => ( + + ))} + + + {(extensionNinjaOrgsConfigResult.isSuccess || + extensionNinjaOrgsConfigResult.isError) && + !extensionNinjaFieldsConfigResult.isFetching && ( + + {extensionNinjaOrgsConfigResult.isSuccess + ? extensionNinjaOrgsConfigResult.data.Results + : 'Error'} + + )} + {(extensionNinjaOrgsAutomapResult.isSuccess || + extensionNinjaOrgsAutomapResult.isError) && ( + + {extensionNinjaOrgsAutomapResult.isSuccess + ? extensionNinjaOrgsAutomapResult.data.Results + : 'Error'} + + )} + + + ) + }} + /> + )} + + - - - - {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 - {listBackendNinjaOrgsResult.isSuccess && - listBackendNinjaOrgsResult.data.Tenants.map((tenant) => ( - - ))} - - - {(extensionNinjaOrgsConfigResult.isSuccess || - extensionNinjaOrgsConfigResult.isError) && - !extensionNinjaFieldsConfigResult.isFetching && ( - - {extensionNinjaOrgsConfigResult.isSuccess - ? extensionNinjaOrgsConfigResult.data.Results - : 'Error'} - - )} - {(extensionNinjaOrgsAutomapResult.isSuccess || - extensionNinjaOrgsAutomapResult.isError) && ( - - {extensionNinjaOrgsAutomapResult.isSuccess - ? extensionNinjaOrgsAutomapResult.data.Results - : 'Error'} - - )} - - - ) - }} - /> - )} - - ) From e44b89d7bd2f6fc9d548837a246ff7aab3b56204 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 23 Apr 2024 13:40:16 -0400 Subject: [PATCH 084/130] auto refresh for recent jobs --- src/components/layout/AppHeader.jsx | 77 +++++++++++++------ .../utilities/CippActionsOffcanvas.jsx | 2 +- 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/src/components/layout/AppHeader.jsx b/src/components/layout/AppHeader.jsx index 85fe747dd98f..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,28 +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, - percent: job.PercentComplete, - progressText: `${job.PercentComplete}%`, - })), - ) } else { - setCippQueueExtendedInfo([ - { label: 'No jobs to display', value: '', timestamp: 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 diff --git a/src/components/utilities/CippActionsOffcanvas.jsx b/src/components/utilities/CippActionsOffcanvas.jsx index dc9567479ab5..f67e3d11ed88 100644 --- a/src/components/utilities/CippActionsOffcanvas.jsx +++ b/src/components/utilities/CippActionsOffcanvas.jsx @@ -231,7 +231,7 @@ export default function CippActionsOffcanvas(props) { - {action.percent && ( + {action?.percent > 0 && (
From 8dd57ad48bc86d068aa69cb33e21819d256f588e Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 23 Apr 2024 20:00:49 +0200 Subject: [PATCH 085/130] Ninjaone mapping changes --- .../SettingsExtensionMappings.jsx | 177 +++++++++++++++--- 1 file changed, 150 insertions(+), 27 deletions(-) diff --git a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx index e29d6bd5198e..6a1a318894b4 100644 --- a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx +++ b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx @@ -20,6 +20,8 @@ export function SettingsExtensionMappings() { const [mappingArray, setMappingArray] = React.useState('defaultMapping') const [mappingValue, setMappingValue] = React.useState({}) const [haloMappingsArray, setHaloMappingsArray] = React.useState([]) + const [ninjaMappingsArray, setNinjaMappingsArray] = React.useState([]) + const [listHaloBackend, listBackendHaloResult = []] = useLazyGenericGetRequestQuery() const [listNinjaOrgsBackend, listBackendNinjaOrgsResult] = useLazyGenericGetRequestQuery() const [listNinjaFieldsBackend, listBackendNinjaFieldsResult] = useLazyGenericGetRequestQuery() @@ -32,7 +34,6 @@ export function SettingsExtensionMappings() { useLazyGenericPostRequestQuery() const onHaloSubmit = () => { - console.log(haloMappingsArray) const originalFormat = haloMappingsArray.reduce((acc, item) => { acc[item.Tenant?.customerId] = { label: item.haloName, value: item.haloId } return acc @@ -43,6 +44,10 @@ export function SettingsExtensionMappings() { }) } const onNinjaOrgsSubmit = (values) => { + const originalFormat = ninjaMappingsArray.reduce((acc, item) => { + acc[item.Tenant?.customerId] = { label: item.haloName, value: item.haloId } + return acc + }, {}) setNinjaOrgsExtensionconfig({ path: 'api/ExecExtensionMapping?AddMapping=NinjaOrgs', values: { mappings: values }, @@ -79,6 +84,24 @@ export function SettingsExtensionMappings() { } }, [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 ( <> @@ -88,9 +111,13 @@ export function SettingsExtensionMappings() { variant="ghost" color="danger" onClick={() => - setHaloMappingsArray((currentHaloMappings) => - currentHaloMappings.filter((item) => item !== row), - ) + row.haloId + ? setHaloMappingsArray((currentHaloMappings) => + currentHaloMappings.filter((item) => item !== row), + ) + : setNinjaMappingsArray((currentNinjaMappings) => + currentNinjaMappings.filter((item) => item !== row), + ) } > @@ -99,7 +126,7 @@ export function SettingsExtensionMappings() { ) } - const columns = [ + const halocolumns = [ { name: 'Tenant', selector: (row) => row.Tenant?.displayName, @@ -135,6 +162,42 @@ export function SettingsExtensionMappings() { }, ] + 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 && @@ -175,7 +238,7 @@ export function SettingsExtensionMappings() { @@ -285,27 +348,92 @@ export function SettingsExtensionMappings() { ) : ( { return ( - Use the table below to map your client to the correct NinjaOne Organization - {listBackendNinjaOrgsResult.isSuccess && - listBackendNinjaOrgsResult.data.Tenants.map((tenant) => ( + 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) && - !extensionNinjaFieldsConfigResult.isFetching && ( + !extensionNinjaOrgsConfigResult.isFetching && ( )} - {(extensionNinjaOrgsAutomapResult.isSuccess || - extensionNinjaOrgsAutomapResult.isError) && ( - - {extensionNinjaOrgsAutomapResult.isSuccess - ? extensionNinjaOrgsAutomapResult.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. + ) }} From 91d04dbe67ce351df3d38f2151e8cb8d317fce1a Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 23 Apr 2024 20:38:41 +0200 Subject: [PATCH 086/130] corrected mapping send --- src/views/cipp/app-settings/SettingsExtensionMappings.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx index 6a1a318894b4..890463fa65e2 100644 --- a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx +++ b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx @@ -43,14 +43,15 @@ export function SettingsExtensionMappings() { values: { mappings: originalFormat }, }) } - const onNinjaOrgsSubmit = (values) => { + const onNinjaOrgsSubmit = () => { const originalFormat = ninjaMappingsArray.reduce((acc, item) => { acc[item.Tenant?.customerId] = { label: item.haloName, value: item.haloId } return acc }, {}) + setNinjaOrgsExtensionconfig({ path: 'api/ExecExtensionMapping?AddMapping=NinjaOrgs', - values: { mappings: values }, + values: { mappings: originalFormat }, }) } From 1562bc63f5848d4fd1e4f4492f22b9d027fc4e95 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 23 Apr 2024 20:44:58 +0200 Subject: [PATCH 087/130] corrected shipped info --- src/views/cipp/app-settings/SettingsExtensionMappings.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx index 890463fa65e2..5e7c600abd22 100644 --- a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx +++ b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx @@ -45,7 +45,7 @@ export function SettingsExtensionMappings() { } const onNinjaOrgsSubmit = () => { const originalFormat = ninjaMappingsArray.reduce((acc, item) => { - acc[item.Tenant?.customerId] = { label: item.haloName, value: item.haloId } + acc[item.Tenant?.customerId] = { label: item.ninjaName, value: item.ninjaId } return acc }, {}) From 8fdecaf159bc6d88b96ec8fccc539c6d67f6da4a Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 23 Apr 2024 21:30:33 +0200 Subject: [PATCH 088/130] automapping --- .../SettingsExtensionMappings.jsx | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx index 5e7c600abd22..aa3191f933b9 100644 --- a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx +++ b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx @@ -73,6 +73,27 @@ export function SettingsExtensionMappings() { }) } + const onHaloAutomap = (values) => { + console.log('starting automap') + //check for a match between the tenant and the halo client based on the tenants displayName property, if so, create the mapping and add to the array. + const newMappings = listBackendHaloResult.data?.Tenants.map( + (tenant) => { + const haloClient = listBackendHaloResult.data?.HaloClients.find( + (client) => client.name === tenant.displayName, + ) + if (haloClient) { + return { + tenant: tenant.customerId, + haloClient: haloClient.label, + haloId: haloClient.value, + } + } + }, + //filter out any undefined values + ).filter((item) => item !== undefined) + setHaloMappingsArray((currentHaloMappings) => [...currentHaloMappings, ...newMappings]) + } + useEffect(() => { if (listBackendHaloResult.isSuccess) { setHaloMappingsArray( @@ -214,12 +235,20 @@ export function SettingsExtensionMappings() { titleType="big" isFetching={listHaloBackend.isFetching} CardButton={ - - {extensionHaloConfigResult.isFetching && ( - - )} - Save Mappings - + <> + + {extensionHaloConfigResult.isFetching && ( + + )} + Save Mappings + + onHaloAutomap()} className="me-2"> + {extensionNinjaOrgsAutomapResult.isFetching && ( + + )} + Automap HaloPSA Clients + + } > {listBackendHaloResult.isFetching ? ( From 2441b136bc12498785726a3cebf0a125c707d70d Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 23 Apr 2024 21:34:22 +0200 Subject: [PATCH 089/130] automapping fixes --- src/views/cipp/app-settings/SettingsExtensionMappings.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx index aa3191f933b9..06410dc9fad2 100644 --- a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx +++ b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx @@ -82,6 +82,8 @@ export function SettingsExtensionMappings() { (client) => client.name === tenant.displayName, ) if (haloClient) { + console.log(haloClient) + console.log(tenant) return { tenant: tenant.customerId, haloClient: haloClient.label, From e0a5fcffa48484deba0bd224aa90f688d1375f2e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 23 Apr 2024 16:55:09 -0400 Subject: [PATCH 090/130] Graph Explorer - Add Reverse Tenant lookups --- src/views/tenant/administration/GraphExplorer.jsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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" /> + + From 922eb3ad13f957f88681e1751c1c1696bf6af942 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 23 Apr 2024 23:14:07 +0200 Subject: [PATCH 091/130] case sensivity is the bane of my existence. --- src/views/cipp/app-settings/SettingsExtensionMappings.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx index 06410dc9fad2..a89e760d00ab 100644 --- a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx +++ b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx @@ -85,8 +85,8 @@ export function SettingsExtensionMappings() { console.log(haloClient) console.log(tenant) return { - tenant: tenant.customerId, - haloClient: haloClient.label, + Tenant: tenant.customerId, + haloName: haloClient.label, haloId: haloClient.value, } } From e30fe0a275c29d9b1df31233a5c1f7ccc0afb58f Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 23 Apr 2024 23:23:25 +0200 Subject: [PATCH 092/130] cleaned console logs --- src/components/tables/CippTable.jsx | 1 - src/components/tables/WizardTableField.jsx | 1 - src/store/api/baseQuery.js | 2 -- src/views/cipp/UserSettings.jsx | 11 ++++------- .../cipp/app-settings/SettingsExtensionMappings.jsx | 3 --- .../email-exchange/connectors/DeployConnector.jsx | 1 - .../email-exchange/spamfilter/DeploySpamfilter.jsx | 1 - .../email-exchange/transport/DeployTransport.jsx | 1 - .../endpoint/applications/ApplicationsAddWinGet.jsx | 2 -- src/views/endpoint/autopilot/AutopilotAddDevice.jsx | 1 - src/views/endpoint/intune/MEMAddPolicy.jsx | 1 - src/views/endpoint/intune/MEMListCompliance.jsx | 1 - src/views/identity/administration/AddUserBulk.jsx | 1 - .../identity/administration/DeployGroupTemplate.jsx | 1 - .../identity/administration/OffboardingWizard.jsx | 1 - src/views/tenant/administration/SecureScore.jsx | 1 - src/views/tenant/conditional/DeployCA.jsx | 1 - src/views/tenant/standards/ListAppliedStandards.jsx | 1 - 18 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/components/tables/CippTable.jsx b/src/components/tables/CippTable.jsx index dbcde884e90d..1651911c7be5 100644 --- a/src/components/tables/CippTable.jsx +++ b/src/components/tables/CippTable.jsx @@ -579,7 +579,6 @@ export default function CippTable({ } const executeselectedAction = (item) => { - // console.log(item) setModalContent({ item, }) 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/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/SettingsExtensionMappings.jsx b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx index a89e760d00ab..4c0919beb0b6 100644 --- a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx +++ b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx @@ -74,7 +74,6 @@ export function SettingsExtensionMappings() { } const onHaloAutomap = (values) => { - console.log('starting automap') //check for a match between the tenant and the halo client based on the tenants displayName property, if so, create the mapping and add to the array. const newMappings = listBackendHaloResult.data?.Tenants.map( (tenant) => { @@ -82,8 +81,6 @@ export function SettingsExtensionMappings() { (client) => client.name === tenant.displayName, ) if (haloClient) { - console.log(haloClient) - console.log(tenant) return { Tenant: tenant.customerId, haloName: haloClient.label, 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/SecureScore.jsx b/src/views/tenant/administration/SecureScore.jsx index 81ac84af7878..a99007b6a251 100644 --- a/src/views/tenant/administration/SecureScore.jsx +++ b/src/views/tenant/administration/SecureScore.jsx @@ -334,7 +334,6 @@ const SecureScore = () => { {info.title} - {console.log(info)} 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 { From c7729b50af1768cff344e2e45cfc996cbc6f220d Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 23 Apr 2024 23:29:24 +0200 Subject: [PATCH 093/130] Automapping callout --- .../cipp/app-settings/SettingsExtensionMappings.jsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx index 4c0919beb0b6..a1b5126c8255 100644 --- a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx +++ b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx @@ -21,7 +21,7 @@ export function SettingsExtensionMappings() { 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() @@ -73,8 +73,8 @@ export function SettingsExtensionMappings() { }) } - const onHaloAutomap = (values) => { - //check for a match between the tenant and the halo client based on the tenants displayName property, if so, create the mapping and add to the array. + const onHaloAutomap = () => { + setHaloAutoMap(true) const newMappings = listBackendHaloResult.data?.Tenants.map( (tenant) => { const haloClient = listBackendHaloResult.data?.HaloClients.find( @@ -323,6 +323,12 @@ export function SettingsExtensionMappings() { + {HaloAutoMap && ( + + Automapping has been executed. Remember to check the changes and save + them. + + )} {(extensionHaloConfigResult.isSuccess || extensionHaloConfigResult.isError) && !extensionHaloConfigResult.isFetching && ( From 4d60d605e63d7611a9f04d7a849b4df4e21baa02 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 23 Apr 2024 17:37:56 -0400 Subject: [PATCH 094/130] Add partner relationships page --- src/_nav.jsx | 5 ++ src/importsMap.jsx | 1 + src/routes.json | 6 ++ .../administration/PartnerRelationships.jsx | 78 +++++++++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 src/views/tenant/administration/PartnerRelationships.jsx diff --git a/src/_nav.jsx b/src/_nav.jsx index 26f7e0764890..da05474f3a34 100644 --- a/src/_nav.jsx +++ b/src/_nav.jsx @@ -162,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/importsMap.jsx b/src/importsMap.jsx index 0121566555bd..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')), diff --git a/src/routes.json b/src/routes.json index 3d8eb476d3fb..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", 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 From 9cb2bbedf42153db3a5857f32a1e129a0a73bdda Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 23 Apr 2024 23:40:10 +0200 Subject: [PATCH 095/130] moved timing of showing success box. --- src/views/cipp/app-settings/SettingsExtensionMappings.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx index a1b5126c8255..c8a275dc14e5 100644 --- a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx +++ b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx @@ -74,16 +74,17 @@ export function SettingsExtensionMappings() { } const onHaloAutomap = () => { - setHaloAutoMap(true) 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.customerId, - haloName: haloClient.label, + haloName: haloClient.name, haloId: haloClient.value, } } @@ -91,6 +92,8 @@ export function SettingsExtensionMappings() { //filter out any undefined values ).filter((item) => item !== undefined) setHaloMappingsArray((currentHaloMappings) => [...currentHaloMappings, ...newMappings]) + + setHaloAutoMap(true) } useEffect(() => { From f7ea986817e2805cc044fcdb213602e6d10caf2f Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 23 Apr 2024 23:48:10 +0200 Subject: [PATCH 096/130] whoops --- src/views/cipp/app-settings/SettingsExtensionMappings.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx index c8a275dc14e5..4676f197a217 100644 --- a/src/views/cipp/app-settings/SettingsExtensionMappings.jsx +++ b/src/views/cipp/app-settings/SettingsExtensionMappings.jsx @@ -83,7 +83,7 @@ export function SettingsExtensionMappings() { console.log(haloClient) console.log(tenant) return { - Tenant: tenant.customerId, + Tenant: tenant, haloName: haloClient.name, haloId: haloClient.value, } From d3ad2f80d76d2318ec35b4dfa12d065f2b6632a7 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 23 Apr 2024 21:41:21 -0400 Subject: [PATCH 097/130] Add API response offcanvas Tooltips for buttons --- src/components/tables/CippTable.jsx | 48 +++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/components/tables/CippTable.jsx b/src/components/tables/CippTable.jsx index dbcde884e90d..b3d38141b17e 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) @@ -605,16 +606,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 +818,20 @@ export default function CippTable({ , ]) } + defaultActions.push([ + + { + setCodeOffcanvasVisible(true) + }} + className="m-1" + size="sm" + > + + + , + ]) return ( <>
@@ -982,6 +999,13 @@ export default function CippTable({ {...rest} /> {selectedRows.length >= 1 && Selected {selectedRows.length} items} + setCodeOffcanvasVisible(false)} + title="API Response" + /> )}
From f305105c52376a6dc9b6fef52515071cfc48968d Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 24 Apr 2024 15:44:08 -0400 Subject: [PATCH 098/130] Recent job details --- src/components/layout/AppHeader.jsx | 1 + .../utilities/CippActionsOffcanvas.jsx | 96 +++++++++++++------ src/components/utilities/CippCodeBlock.jsx | 37 ++++--- .../utilities/CippTableOffcanvas.jsx | 42 +++++++- 4 files changed, 130 insertions(+), 46 deletions(-) diff --git a/src/components/layout/AppHeader.jsx b/src/components/layout/AppHeader.jsx index 9661c89e7eba..620a2c9332c7 100644 --- a/src/components/layout/AppHeader.jsx +++ b/src/components/layout/AppHeader.jsx @@ -117,6 +117,7 @@ const AppHeader = () => { timestamp: job.Timestamp, percent: job.PercentComplete, progressText: `${job.PercentComplete}%`, + detailsObject: job.Tasks, })), ) } else { diff --git a/src/components/utilities/CippActionsOffcanvas.jsx b/src/components/utilities/CippActionsOffcanvas.jsx index f67e3d11ed88..7a78192aba10 100644 --- a/src/components/utilities/CippActionsOffcanvas.jsx +++ b/src/components/utilities/CippActionsOffcanvas.jsx @@ -21,7 +21,12 @@ import { CRow, CSpinner, } from '@coreui/react' -import { CippCodeBlock, CippOffcanvas, ModalService } from 'src/components/utilities' +import { + CippCodeBlock, + CippOffcanvas, + CippTableOffcanvas, + ModalService, +} from 'src/components/utilities' import { CippOffcanvasPropTypes } from 'src/components/utilities/CippOffcanvas' import { CippOffcanvasTable } from 'src/components/tables' import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' @@ -31,6 +36,65 @@ import ReactTimeAgo from 'react-time-ago' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faGlobe } from '@fortawesome/free-solid-svg-icons' +const CippOffcanvasCard = ({ action, key }) => { + const [offcanvasVisible, setOffcanvasVisible] = useState(false) + return ( + <> + + + Report Name: {action.label} + + + + {action.value && ( + <> + {action?.link ? ( + Status: {action.value} + ) : ( + Status: {action.value} + )} + + )} + + {Array.isArray(action?.detailsObject) && ( + setOffcanvasVisible(true)}> + Details + + )} + {Array.isArray(action?.detailsObject) && ( + setOffcanvasVisible(false)} + /> + )} + + + + {action?.percent > 0 && ( + +
+ + {action?.progressText} + +
+
+ )} + + {action.timestamp && } + +
+
+
+ + ) +} +CippOffcanvasCard.propTypes = { + action: PropTypes.object, + key: PropTypes.object, +} + export default function CippActionsOffcanvas(props) { const inputRef = useRef('') const [genericGetRequest, getResults] = useLazyGenericGetRequestQuery() @@ -219,34 +283,7 @@ export default function CippActionsOffcanvas(props) { let cardContent try { cardContent = props.cards.map((action, index) => ( - <> - - - Report Name: {action.label} - - - - {action.value && Status: {action.value}} - - - - - {action?.percent > 0 && ( - -
- - {action?.progressText} - -
-
- )} - - {action.timestamp && } - -
-
-
- + )) } catch (error) { // swallow error @@ -343,6 +380,7 @@ export default function CippActionsOffcanvas(props) { diff --git a/src/components/utilities/CippCodeBlock.jsx b/src/components/utilities/CippCodeBlock.jsx index 00ccd3c54f7c..dbfc80d210e4 100644 --- a/src/components/utilities/CippCodeBlock.jsx +++ b/src/components/utilities/CippCodeBlock.jsx @@ -1,11 +1,12 @@ import React, { useState } from 'react' import PropTypes from 'prop-types' import { CopyToClipboard } from 'react-copy-to-clipboard' -import { CButton, CCallout } from '@coreui/react' +import { CButton } from '@coreui/react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCopy, faClipboard } from '@fortawesome/free-regular-svg-icons' import SyntaxHighlighter from 'react-syntax-highlighter' import { atomOneDark } from 'react-syntax-highlighter/dist/esm/styles/hljs' +import { CippCallout } from '../layout' function CippCodeBlock({ code, @@ -16,7 +17,7 @@ function CippCodeBlock({ callout = false, calloutColour = 'info', calloutCopyValue = false, - dismissable = false, + calloutDismissible = false, }) { const [codeCopied, setCodeCopied] = useState(false) @@ -27,20 +28,26 @@ function CippCodeBlock({ return (
- onCodeCopied()}> - - {codeCopied ? : } - - + {!calloutDismissible && ( + onCodeCopied()}> + + {codeCopied ? ( + + ) : ( + + )} + + + )} {callout && ( - + {code} - + )} {!callout && ( { + return { + Key: key, + Value: data[key], + } + }) + } else { + if (Array.isArray(data) && typeof data[0] !== 'object') { + data = data.map((row) => { + return { + Value: row, + } + }) + } + } + columns = [] + Object.keys(data[0]).map((key) => { + columns.push({ + name: key, + selector: (row) => row[key], + sortable: true, + exportSelector: key, + cell: cellGenericFormatter(), + }) + }) + } + return ( <> - + <> + {data !== null && data !== undefined ? ( + + ) : ( + + )} + ) @@ -35,6 +72,7 @@ CippTableOffcanvas.propTypes = { params: PropTypes.object, columns: PropTypes.object, tableProps: PropTypes.object, + data: PropTypes.object, } export default CippTableOffcanvas From fbf2158ac1639c22532dfcbce1a8b17af25f3912 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 24 Apr 2024 22:52:33 +0200 Subject: [PATCH 099/130] improved standards templates --- .../tenant/standards/ListAppliedStandards.jsx | 148 ++++++++++-------- 1 file changed, 86 insertions(+), 62 deletions(-) diff --git a/src/views/tenant/standards/ListAppliedStandards.jsx b/src/views/tenant/standards/ListAppliedStandards.jsx index 3745c099aec7..33bb5b165403 100644 --- a/src/views/tenant/standards/ListAppliedStandards.jsx +++ b/src/views/tenant/standards/ListAppliedStandards.jsx @@ -13,6 +13,7 @@ import { CWidgetStatsB, CBadge, CFormInput, + CTooltip, } from '@coreui/react' import { Form, FormSpy } from 'react-final-form' import { @@ -29,7 +30,7 @@ import { useLazyGenericPostRequestQuery, } from 'src/store/api/app' import { faCheck, faCircleNotch, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons' -import { CippContentCard, CippPage } from 'src/components/layout' +import { CippCallout, CippContentCard, CippPage } from 'src/components/layout' import { useSelector } from 'react-redux' import { ModalService, validateAlphabeticalSort } from 'src/components/utilities' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' @@ -39,6 +40,7 @@ import allStandardsList from 'src/data/standards' import CippCodeOffCanvas from 'src/components/utilities/CippCodeOffcanvas' import GDAPRoles from 'src/data/GDAPRoles' import Select from 'react-select' +import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat' const DeleteAction = () => { const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) @@ -91,32 +93,78 @@ const ApplyNewStandard = () => { onConfirm: () => execStandards({ path: `api/ExecStandardsRun?Tenantfilter=${selectedTenant}` }), }) - const ourRef = useRef() - const TemplateModal = () => + + const Offcanvas = (row, rowIndex, formatExtraData) => { + const handleDeleteIntuneTemplate = (apiurl, message) => { + ModalService.confirm({ + title: 'Confirm', + body:
{message}
, + onConfirm: () => ExecuteGetRequest({ path: apiurl }).then(() => refetchStandards()), + confirmLabel: 'Continue', + cancelLabel: 'Cancel', + }) + } + return ( + <> + + setTemplateStandard(row)} + > + + + + + handleDeleteIntuneTemplate( + `/api/RemoveStandardTemplate?ID=${row.GUID}`, + 'Do you want to delete the template?', + ) + } + > + + + + ) + } + const TemplateModal = () => { + const columns = [ + { + name: 'name', + selector: (row) => row['name'], + sortable: true, + exportSelector: 'name', + cell: cellGenericFormatter(), + }, + { + name: 'GUID', + selector: (row) => row['GUID'], + sortable: true, + exportSelector: 'GUID', + omit: true, + }, + { + name: 'Actions', + cell: Offcanvas, + maxWidth: '80px', + }, + ] + ModalService.open({ - body: ( -
- {isFetching && } - {isError && 'Something went wrong loading your templates'} - {isSuccess && ( -