Skip to content

Commit

Permalink
Merge pull request KelvinTegelaar#1851 from johnduprey/dev
Browse files Browse the repository at this point in the history
Mailbox Restores
  • Loading branch information
KelvinTegelaar committed Nov 3, 2023
2 parents 75bef6e + 2ce6d21 commit feb16cd
Show file tree
Hide file tree
Showing 6 changed files with 429 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/_nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,25 @@ const _nav = [
},
],
},
{
component: CNavGroup,
name: 'Tools',
section: 'Tools',
to: '/email/tools',
icon: <FontAwesomeIcon icon={faToolbox} className="nav-icon" />,
items: [
{
component: CNavItem,
name: 'Mailbox Restore Wizard',
to: '/email/tools/mailbox-restore-wizard',
},
{
component: CNavItem,
name: 'Mailbox Restores',
to: '/email/tools/mailbox-restores',
},
],
},
{
component: CNavTitle,
name: 'Settings',
Expand Down
7 changes: 7 additions & 0 deletions src/components/utilities/CippActionsOffcanvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ export default function CippActionsOffcanvas(props) {
title: 'Confirm',
onConfirm: () => genericGetRequest({ path: modalUrl }),
})
} else if (modalType === 'codeblock') {
ModalService.open({
data: modalBody,
componentType: 'codeblock',
title: 'Info',
size: 'lg',
})
} else {
ModalService.confirm({
key: modalContent,
Expand Down
5 changes: 5 additions & 0 deletions src/components/utilities/SharedModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import { CButton, CModal, CModalBody, CModalFooter, CModalHeader, CModalTitle } from '@coreui/react'
import PropTypes from 'prop-types'
import { CippTable } from 'src/components/tables'
import CippCodeBlock from 'src/components/utilities/CippCodeBlock'

/**
*
Expand All @@ -18,6 +19,10 @@ function mapBodyComponent({ componentType, data, componentProps }) {
return <div>{Array.isArray(data) && data.map((el, idx) => <div key={idx}>{el}</div>)}</div>
case 'text':
return String(data)
case 'codeblock':
return (
<CippCodeBlock language="text" code={data} showLineNumbers={false} {...componentProps} />
)
default:
return String(data)
}
Expand Down
14 changes: 14 additions & 0 deletions src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,10 @@ const ServiceHealth = React.lazy(() => import('src/views/tenant/administration/S
const EnterpriseApplications = React.lazy(() =>
import('src/views/tenant/administration/ListEnterpriseApps'),
)
const MailboxRestoreWizard = React.lazy(() =>
import('src/views/email-exchange/tools/MailboxRestoreWizard'),
)
const MailboxRestores = React.lazy(() => import('src/views/email-exchange/tools/MailboxRestores'))

const routes = [
// { path: '/', exact: true, name: 'Home' },
Expand Down Expand Up @@ -544,6 +548,16 @@ const routes = [
name: 'List Spamfilter Templates',
component: SpamFilterTemplate,
},
{
path: '/email/tools/mailbox-restore-wizard',
name: 'Mailbox Restore Wizard',
component: MailboxRestoreWizard,
},
{
path: '/email/tools/mailbox-restores',
name: 'Mailbox Restores',
component: MailboxRestores,
},
{
path: '/email/spamfilter/add-template',
name: 'Add Spamfilter Template',
Expand Down
214 changes: 214 additions & 0 deletions src/views/email-exchange/tools/MailboxRestoreWizard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import React from 'react'
import { CCallout, CCol, CListGroup, CListGroupItem, CRow, CSpinner } 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,
RFFCFormSelect,
RFFCFormSwitch,
RFFSelectSearch,
} from 'src/components/forms'
import { TenantSelector } from 'src/components/utilities'
import { useListUsersQuery } from 'src/store/api/users'
import { useGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app'

const Error = ({ name }) => (
<Field
name={name}
subscription={{ touched: true, error: true }}
render={({ meta: { touched, error } }) =>
touched && error ? (
<CCallout color="danger">
<FontAwesomeIcon icon={faExclamationTriangle} color="danger" />
{error}
</CCallout>
) : null
}
/>
)

Error.propTypes = {
name: PropTypes.string.isRequired,
}

const MailboxRestoreWizard = () => {
const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName)
const {
data: sourceMailboxes = [],
isFetching: sMailboxesIsFetching,
error: sMailboxError,
} = useGenericGetRequestQuery({
path: '/api/ListMailboxes',
params: { TenantFilter: tenantDomain, SoftDeletedMailbox: true },
})
const {
data: targetMailboxes = [],
isFetching: tMailboxesIsFetching,
error: tMailboxError,
} = useGenericGetRequestQuery({
path: '/api/ListMailboxes',
params: { TenantFilter: tenantDomain },
})
const currentSettings = useSelector((state) => state.app)
const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()

const handleSubmit = async (values) => {
const shippedValues = {
TenantFilter: tenantDomain,
RequestName: values.RequestName,
SourceMailbox: values.SourceMailbox.value,
TargetMailbox: values.TargetMailbox.value,
BadItemLimit: values.BadItemLimit,
LargeItemLimit: values.LargeItemLimit,
AcceptLargeDataLoss: values.AcceptLargeDataLoss,
}

//alert(JSON.stringify(values, null, 2))
genericPostRequest({ path: '/api/ExecMailboxRestore', values: shippedValues })
}

return (
<CippWizard onSubmit={handleSubmit} wizardTitle="Mailbox Restore Wizard">
<CippWizard.Page
title="Tenant Choice"
description="Choose the tenant to perform a mailbox restore"
>
<center>
<h3 className="text-primary">Step 1</h3>
<h5 className="card-title mb-4">Choose a tenant</h5>
</center>
<hr className="my-4" />
<Field name="tenantFilter">{(props) => <TenantSelector />}</Field>
<hr className="my-4" />
</CippWizard.Page>
<CippWizard.Page
title="Source Mailbox"
description="Select a soft deleted mailbox to restore."
>
<center>
<h3 className="text-primary">Step 2</h3>
<h5>Select a soft deleted mailbox to restore.</h5>
</center>
<hr className="my-4" />
<div className="mb-2">
<RFFSelectSearch
label={'Soft Deleted Mailboxes in ' + tenantDomain}
values={sourceMailboxes?.map((mbx) => ({
value: mbx.ExchangeGuid,
name: `${mbx.displayName} <${mbx.UPN}>`,
}))}
placeholder={!sMailboxesIsFetching ? 'Select mailbox' : 'Loading...'}
name="SourceMailbox"
/>
{sMailboxError && <span>Failed to load source mailboxes</span>}
</div>
<hr className="my-4" />
</CippWizard.Page>
<CippWizard.Page title="Target Mailbox" description="Select a mailbox to restore to.">
<center>
<h3 className="text-primary">Step 2</h3>
<h5>Select a mailbox to restore to.</h5>
</center>
<hr className="my-4" />
<div className="mb-2">
<RFFSelectSearch
label={'Mailboxes in ' + tenantDomain}
values={targetMailboxes?.map((mbx) => ({
value: mbx.ExchangeGuid,
name: `${mbx.displayName} <${mbx.UPN}>`,
}))}
placeholder={!tMailboxesIsFetching ? 'Select mailbox' : 'Loading...'}
name="TargetMailbox"
/>
{sMailboxError && <span>Failed to load source mailboxes</span>}
</div>
<hr className="my-4" />
</CippWizard.Page>
<CippWizard.Page title="Restore Request Options" description="">
<center>
<h3 className="text-primary">Step 3</h3>
<h5>Enter Restore Request Options</h5>
</center>
<hr className="my-4" />
<div className="mb-2">
<CRow>
<CCol md={6}>
<RFFCFormInput type="text" name="RequestName" label="Restore Request Name" />
</CCol>
</CRow>
<CRow>
<CCol md={6}>
<RFFCFormSwitch name="AcceptLargeDataLoss" label="Accept Large Data Loss" />
</CCol>
</CRow>
<CRow>
<CCol md={6}>
<RFFCFormInput name="BadItemLimit" label="Bad Item Limit" />
</CCol>
</CRow>
<CRow>
<CCol md={6}>
<RFFCFormInput name="LargeItemLimit" label="Large Item Limit" />
</CCol>
</CRow>
</div>
<hr className="my-4" />
</CippWizard.Page>
<CippWizard.Page title="Review and Confirm" description="Confirm the settings to apply">
<center>
<h3 className="text-primary">Step 4</h3>
<h5 className="mb-4">Confirm and apply</h5>
<hr className="my-4" />
</center>
<div className="mb-2">
{postResults.isFetching && (
<CCallout color="info">
<CSpinner>Loading</CSpinner>
</CCallout>
)}
{postResults.isSuccess && (
<CCallout color="success">
{postResults.data.Results.map((message, idx) => {
return <li key={idx}>{message}</li>
})}
</CCallout>
)}
{!postResults.isSuccess && (
<FormSpy>
{(props) => (
<>
<CRow>
<CCol md={{ span: 6, offset: 3 }}>
<CListGroup flush>
<CListGroupItem className="d-flex justify-content-between align-items-center">
<h5 className="mb-0">Selected Tenant:</h5>
{tenantDomain}
</CListGroupItem>
<CListGroupItem className="d-flex justify-content-between align-items-center">
<h5 className="mb-0">Source Mailbox:</h5>
{props.values.SourceMailbox.label}
</CListGroupItem>
<CListGroupItem className="d-flex justify-content-between align-items-center">
<h5 className="mb-0">Target Mailbox:</h5>
{props.values.TargetMailbox.label}
</CListGroupItem>
</CListGroup>
</CCol>
</CRow>
</>
)}
</FormSpy>
)}
</div>
<hr className="my-4" />
</CippWizard.Page>
</CippWizard>
)
}

export default MailboxRestoreWizard
Loading

0 comments on commit feb16cd

Please sign in to comment.