diff --git a/src/_nav.jsx b/src/_nav.jsx index 2f99dffd0953..902a8390b79f 100644 --- a/src/_nav.jsx +++ b/src/_nav.jsx @@ -150,7 +150,7 @@ const _nav = [ { component: CNavItem, name: 'Tenant Onboarding', - to: '/tenant/administration/tenant-onboarding-wizard', + to: '/tenant/administration/tenant-onboarding', }, { component: CNavItem, diff --git a/src/components/tables/CippTable.jsx b/src/components/tables/CippTable.jsx index c977c700cc87..f3ea486ee243 100644 --- a/src/components/tables/CippTable.jsx +++ b/src/components/tables/CippTable.jsx @@ -125,6 +125,7 @@ export default function CippTable({ filterlist, showFilter = true, endpointName, + defaultSortAsc = true, tableProps: { keyField = 'id', theme = 'cyberdrain', @@ -989,7 +990,7 @@ export default function CippTable({ expandableRowsComponent={expandableRowsComponent} highlightOnHover={highlightOnHover} expandOnRowClicked={expandOnRowClicked} - defaultSortAsc + defaultSortAsc={defaultSortAsc} defaultSortFieldId={1} sortFunction={customSort} paginationPerPage={tablePageSize} @@ -1050,6 +1051,7 @@ export const CippTablePropTypes = { disableCSVExport: PropTypes.bool, error: PropTypes.object, filterlist: PropTypes.arrayOf(PropTypes.object), + defaultSortAsc: PropTypes.bool, } CippTable.propTypes = CippTablePropTypes diff --git a/src/importsMap.jsx b/src/importsMap.jsx index 7b7589049b42..fd44014ae9c7 100644 --- a/src/importsMap.jsx +++ b/src/importsMap.jsx @@ -140,6 +140,7 @@ import React from 'react' "/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": React.lazy(() => import('./views/tenant/administration/TenantOnboarding')), "/tenant/administration/tenant-onboarding-wizard": React.lazy(() => import('./views/tenant/administration/TenantOnboardingWizard')), } export default importsMap \ No newline at end of file diff --git a/src/routes.json b/src/routes.json index e1d382459781..e681555e0346 100644 --- a/src/routes.json +++ b/src/routes.json @@ -954,6 +954,12 @@ "component": "views/tenant/administration/TenantOffboardingWizard", "allowedRoles": ["admin"] }, + { + "path": "/tenant/administration/tenant-onboarding", + "name": "Tenant Onboarding", + "component": "views/tenant/administration/TenantOnboarding", + "allowedRoles": ["admin"] + }, { "path": "/tenant/administration/tenant-onboarding-wizard", "name": "Tenant Onboarding", diff --git a/src/views/tenant/administration/TenantOnboarding.jsx b/src/views/tenant/administration/TenantOnboarding.jsx new file mode 100644 index 000000000000..94afd7437938 --- /dev/null +++ b/src/views/tenant/administration/TenantOnboarding.jsx @@ -0,0 +1,153 @@ +import { CBadge, CTooltip } from '@coreui/react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import React from 'react' +import { TitleButton } from 'src/components/buttons' +import { CippPageList } from 'src/components/layout' +import { CellBadge, cellDateFormatter } from 'src/components/tables' +import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat' + +const TenantOnboarding = () => { + const titleButton = ( + + ) + function ucfirst(str) { + return str.charAt(0).toUpperCase() + str.slice(1) + } + function getBadgeColor(status) { + switch (status.toLowerCase()) { + case 'queued': + return 'info' + case 'failed': + return 'danger' + case 'succeeded': + return 'success' + case 'running': + return 'primary' + } + } + function getLatestStep(steps) { + var activeSteps = steps?.filter((step) => step.Status !== 'pending') + var currentStep = activeSteps[activeSteps.length - 1] + var color = 'info' + var icon = 'me-2 info-circle' + var spin = false + switch (currentStep?.Status) { + case 'succeeded': + color = 'me-2 text-success' + icon = 'check-circle' + break + case 'failed': + color = 'me-2 text-danger' + icon = 'times-circle' + break + case 'running': + color = 'me-2 text-primary' + icon = 'sync' + spin = true + break + } + return ( + +
+ + {currentStep?.Title} +
+
+ ) + } + const columns = [ + { + name: 'Last Update', + selector: (row) => row.Timestamp, + sortable: true, + exportSelector: 'Timestamp', + cell: cellDateFormatter({ format: 'short' }), + }, + { + name: 'Tenant', + selector: (row) => row?.Relationship?.customer?.displayName, + sortable: true, + cell: cellGenericFormatter(), + exportSelector: 'Relationship/customer/displayName', + }, + { + name: 'Status', + selector: (row) => row?.Status, + sortable: true, + exportSelector: 'Status', + cell: (row) => CellBadge({ label: ucfirst(row?.Status), color: getBadgeColor(row?.Status) }), + }, + { + name: 'Onboarding Step', + selector: (row) => row?.OnboardingSteps, + cell: (row) => getLatestStep(row?.OnboardingSteps), + }, + { + name: 'Logs', + selector: (row) => row?.Logs, + sortable: false, + cell: cellGenericFormatter(), + }, + ] + return ( +
+ +
+ ) +} + +export default TenantOnboarding diff --git a/src/views/tenant/administration/TenantOnboardingWizard.jsx b/src/views/tenant/administration/TenantOnboardingWizard.jsx index 122765dff6c3..874510c3517e 100644 --- a/src/views/tenant/administration/TenantOnboardingWizard.jsx +++ b/src/views/tenant/administration/TenantOnboardingWizard.jsx @@ -1,32 +1,16 @@ -import React, { useState, useRef, useEffect } from 'react' -import { - CAccordion, - CAccordionBody, - CAccordionHeader, - CAccordionItem, - CButton, - CCallout, - CCol, - CRow, - CSpinner, -} from '@coreui/react' +import React, { useRef, useEffect } from 'react' +import { CAccordion, CCallout, CCol, CRow } from '@coreui/react' import { Field, FormSpy } from 'react-final-form' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faExclamationTriangle, faTimes, faCheck } from '@fortawesome/free-solid-svg-icons' import { useSelector } from 'react-redux' import { CippWizard } from 'src/components/layout' import PropTypes from 'prop-types' -import { RFFCFormCheck, RFFCFormInput, RFFCFormSwitch, RFFSelectSearch } from 'src/components/forms' -import { CippCodeBlock, TenantSelector } from 'src/components/utilities' +import { RFFCFormSwitch } from 'src/components/forms' import { useLazyGenericPostRequestQuery } from 'src/store/api/app' -import { - CellDate, - WizardTableField, - cellDateFormatter, - cellNullTextFormatter, -} from 'src/components/tables' -import ReactTimeAgo from 'react-time-ago' -import { TableModalButton, TitleButton } from 'src/components/buttons' +import { WizardTableField, cellDateFormatter, cellNullTextFormatter } from 'src/components/tables' +import { TitleButton } from 'src/components/buttons' +import RelationshipOnboarding from 'src/views/tenant/administration/onboarding/RelationshipOnboarding' const Error = ({ name }) => ( { - const [relationshipReady, setRelationshipReady] = useState(false) - const [refreshGuid, setRefreshGuid] = useState(false) - const [getOnboardingStatus, onboardingStatus] = useLazyGenericPostRequestQuery() - var headerIcon = relationshipReady ? 'check-circle' : 'question-circle' - - useInterval( - async () => { - if (onboardingStatus.data?.Status == 'running' || onboardingStatus.data?.Status == 'queued') { - getOnboardingStatus({ - path: '/api/ExecOnboardTenant', - values: { id: relationship.id }, - }) - } - }, - 5000, - onboardingStatus.data, - ) - - return ( - - - {onboardingStatus?.data?.Status == 'running' ? ( - - ) : ( - - )} - Onboarding Relationship: {} - {relationship.displayName} - - - - {(relationship?.customer?.displayName || - onboardingStatus?.data?.Relationship?.customer?.displayName) && ( - -

Customer

- {onboardingStatus?.data?.Relationship?.customer?.displayName - ? onboardingStatus?.data?.Relationship?.customer?.displayName - : relationship.customer.displayName} -
- )} - {onboardingStatus?.data?.Timestamp && ( - -

Last Updated

- -
- )} - -

Relationship Status

- {relationship.status} -
- -

Creation Date

- -
- {relationship.status == 'approvalPending' && - onboardingStatus?.data?.Relationship?.status != 'active' && ( - -

Invite URL

- -
- )} -
- {onboardingStatus.isUninitialized && - getOnboardingStatus({ - path: '/api/ExecOnboardTenant', - values: { id: relationship.id, gdapRoles, autoMapRoles, addMissingGroups }, - })} - {onboardingStatus.isSuccess && ( - <> - {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 && ( - - )} -
- {onboardingStatus.data?.OnboardingSteps?.map((step, idx) => ( - - - {step.Status == 'running' ? ( - - ) : ( - - )}{' '} - {step.Title} - - - {step.Message} - - - ))} - - )} -
-
- ) -} -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) const currentSettings = useSelector((state) => state.app) @@ -350,6 +183,11 @@ const TenantOnboardingWizard = () => {
Tenant Onboarding Options

+
Optional Settings
+

+ Use these options for relationships created outside of the CIPP Invite Wizard or if the + SAM user is missing required GDAP groups from the Partner Tenant. +

diff --git a/src/views/tenant/administration/onboarding/RelationshipOnboarding.jsx b/src/views/tenant/administration/onboarding/RelationshipOnboarding.jsx new file mode 100644 index 000000000000..3f5c93f4ab12 --- /dev/null +++ b/src/views/tenant/administration/onboarding/RelationshipOnboarding.jsx @@ -0,0 +1,195 @@ +import React, { useState, useRef, useEffect } from 'react' +import { + CAccordionBody, + CAccordionHeader, + CAccordionItem, + CButton, + CCallout, + CCol, + CRow, + CSpinner, +} from '@coreui/react' +import { Field } from 'react-final-form' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faExclamationTriangle, faTimes, faCheck } from '@fortawesome/free-solid-svg-icons' +import PropTypes from 'prop-types' +import { CippCodeBlock, TenantSelector } from 'src/components/utilities' +import { useLazyGenericPostRequestQuery } from 'src/store/api/app' +import { CellDate } from 'src/components/tables' +import ReactTimeAgo from 'react-time-ago' +import { TableModalButton, TitleButton } from 'src/components/buttons' + +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]) +} + +const RelationshipOnboarding = ({ relationship, gdapRoles, autoMapRoles, addMissingGroups }) => { + const [relationshipReady, setRelationshipReady] = useState(false) + const [refreshGuid, setRefreshGuid] = useState(false) + const [getOnboardingStatus, onboardingStatus] = useLazyGenericPostRequestQuery() + var headerIcon = relationshipReady ? 'check-circle' : 'question-circle' + + useInterval( + async () => { + if (onboardingStatus.data?.Status == 'running' || onboardingStatus.data?.Status == 'queued') { + getOnboardingStatus({ + path: `/api/ExecOnboardTenant`, + values: { id: relationship.id }, + }) + } + }, + 5000, + onboardingStatus.data, + ) + + return ( + + + {onboardingStatus?.data?.Status == 'running' ? ( + + ) : ( + + )} + Onboarding Relationship: {} + {relationship.displayName} + + + + {(relationship?.customer?.displayName || + onboardingStatus?.data?.Relationship?.customer?.displayName) && ( + +

Customer

+ {onboardingStatus?.data?.Relationship?.customer?.displayName + ? onboardingStatus?.data?.Relationship?.customer?.displayName + : relationship.customer.displayName} +
+ )} + {onboardingStatus?.data?.Timestamp && ( + +

Last Updated

+ +
+ )} + +

Relationship Status

+ {relationship.status} +
+ +

Creation Date

+ +
+ {relationship.status == 'approvalPending' && + onboardingStatus?.data?.Relationship?.status != 'active' && ( + +

Invite URL

+ +
+ )} +
+ {onboardingStatus.isUninitialized && + getOnboardingStatus({ + path: '/api/ExecOnboardTenant', + values: { id: relationship.id, gdapRoles, autoMapRoles, addMissingGroups }, + })} + {onboardingStatus.isSuccess && ( + <> + {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 && ( + + )} +
+ {onboardingStatus.data?.OnboardingSteps?.map((step, idx) => ( + + + {step.Status == 'running' ? ( + + ) : ( + + )}{' '} + {step.Title} + + + {step.Message} + + + ))} + + )} +
+
+ ) +} +RelationshipOnboarding.propTypes = { + relationship: PropTypes.object.isRequired, + gdapRoles: PropTypes.array, + autoMapRoles: PropTypes.bool, + addMissingGroups: PropTypes.bool, + statusOnly: PropTypes.bool, +} + +export default RelationshipOnboarding